/*********************************************************************}
{                                                                     }
{  JavaToDPR - Generates a Delphi project file from a Java class      }
{              class file.                                            }
{                                                                     }
{  JavaToDPR produces an Object Pascal file from a Java class.        }
{  This file forms the basis that allows Java and Delphi-produced     }
{  code to interact via the Java Native Interface.                    }
{                                                                     }
{  This functionality is similar to the javah tool from Sun           }
{  Microsystems, Inc., which produces C header files from Java        }
{  class files. Some code is based on JNI2Pas which was written       }
{  by Jeffrey Schwartz                                                }
{                                                                     }
{ Copyright (C) 2001 MMG and Associates                               }
{ www.pacifier.com/~mmead/jni/delphi                                  }
{                                                                     }
{ Portions Copyright (C) 2001 Jeffrey Schwartz                        }
{ www.jeffschwartz.net (me@jeffschwartz.net)                          }
{                                                                     }
{ dollarsToTwentyFours, stretchUnderscores and addSigIfNeeded         }
{ Copyright (C) 2005 Daniel U. Thibault                               }
{ www.bigfoot.com/~D.U.Thibault (D.U.Thibault@Bigfoot.com)            }
{                                                                     }
{ The contents of this file are used with permission, subject to      }
{ the Mozilla Public License Version 1.1 (the "License"); you may     }
{ not use this file except in compliance with the License. You may    }
{ obtain a copy of the License at                                     }
{ http://www.mozilla.org/NPL/NPL-1_1Final.html                        }
{                                                                     }
{ Software distributed under the License is distributed on an         }
{ "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or      }
{ implied. See the License for the specific language governing        }
{ rights and limitations under the License.                           }
{                                                                     }
{ History:                                                            }
{   25 Feb 2005 - fixed bug in mapJavaTypeToSigType                   }
{                 (did not replace periods with slashes)              }
{   23 Feb 2005 - addSigIfNeeded added to handle overloaded methods   }
{                 stretchUnderscores to handle underscores in names   }
{   18 Feb 2005 - dollarsToTwentyFours added to handle inner classes  }
{   06 May 2001 - Created.                                            }
{                                                                     }
{*********************************************************************/

/**
 * Title:        JavaToDPR
 * Description:  JavaToDPR is an Object Pascal version of javah (from Sun Microsystem's JDK) that implements a subset of javah, primarily the generation of function/procedure declarations from native methods declared in a Java class file. The resulting file is a Delphi project file (.dpr)
 * Copyright:    Copyright (c) 2001-2005
 * Company:      MMG and Associates
 * @author Matthew Mead; Daniel U. Thibault
 * @version 1.0.3
 */

import java.io.FileWriter;
import java.io.PrintWriter;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Enumeration;
import java.util.Vector;

//*********************************************************************************************************************
//*********************************************************************************************************************
//*********************************************************************************************************************
// JavaToDPR
//

  /**
   * JavaToDPR is the public interface to the set of Java classes that extract native method declarations
   * from a Java class file and produce a Delphi project file, a .dpr file. This class wraps the other
   * classes and provides a simple interface for the user. The result of using this class is a Delphi
   * project file that can immediately be compiled to a native DLL. The programmer must still implement
   * the stub functions/procedures.
   */
public class JavaToDPR
{
    /**
     *  This is the starting point for the processing. The command line arguments control how
     *  the other classes extract the methods from a Java class file to produce the Delphi
     *  project file.
     */
  public static void main(String[] args) {
    JavaToDPR javaToDPR = new JavaToDPR();
    try {
      if (!javaToDPR.parseCommandLine(args))
        System.exit(-1);

      javaToDPR.extractMethods();
    }
    catch (Exception e) {
      e.printStackTrace();
    }
  }

    /**
     *  The name of the class to process.
     */
  private String className = "";

    /**
     *  If the output is directed to a file, this is the name. Default is empty string.
     *  (Output is sent to System.out)
     */
  private String outputFilename = "";

    /**
     *  The target platform of the generated code (Linux, Win32, or ALL.) Default is ALL.
     */
  private String platform = "ALL";

    /**
     *  Flag indicating verbose output. Default is <b>false</b>.
     */
  private boolean verboseOutput = false;

    /**
     *  Flag controlling the method extraction. If true, all methods (not just native) are
     *  retrieved from the class file. Default is <b>false</b>.
     */
  private boolean includeNonNativeMethods = false;

    /**
     *  A constant string representing the version of this tool.
     */
  private final String VERSION = "1.0.3";

    /**
     *  The vector of methods that were retrieved from the class file.
     */
  private NativeMethods nativeMethods;

    /**
     *  The destination of the output.
     */
  private PrintWriter outputStream;

    /**
     *  A method used during debugging. Simply outputs the private member fields.
     */
  public void dump() {
    System.out.println("className: " + className);
    System.out.println("outputFilename: " + outputFilename);
    System.out.println("verboseOutput: " + verboseOutput);
    System.out.println("includeNonNativeMethods: " + includeNonNativeMethods);
    System.out.println("platform: " + platform);
  }

    /**
     *  Displays a usage screen.
     */
  private void displayHelp() {
    System.out.println("Usage: JavaToDPR [options] class");
    System.out.println();
    System.out.println("where [options] include:");
    System.out.println();
    System.out.println("        -help                 Print this help message");
    System.out.println("        -o <file>             Output file (default is stdout)");
    System.out.println("        -version              Print version information then quit");
    System.out.println("        -verbose              Enable verbose output");
    System.out.println("        -all                  Include non-native methods (internal debugging)");
    System.out.println("        -platform <platform>  For code generation (Linux|Win32|All)");
    System.out.println();
    System.out.println("<classes> are specified with their fully qualified names (for instance, java.awt.Rectangle).");
    System.out.println();
  }

    /**
     *  Displays the version information.
     */
  private void displayVersion() {
    System.out.println("Version " + VERSION);
  }

    /**
     *  Displays an error message.
     */
  private void displayError(String errMessage) {
    System.err.println(errMessage);
  }

    /**
     *  Parses the command line and sets the private fields from the arguments.
     *
     * @param args[] This is the array of strings that was passed to the
     * <b>public static</b> main method.
     * @return Returns true if execution should continue, false if execution should stop.
     */
  private boolean parseCommandLine(String args[]) throws CommandLineException {

    if (args.length < 1) {
      displayHelp();
      return false;
    }

    if (args[args.length - 1].indexOf('-') != 0)
      className = args[args.length - 1];

    for (int i = 0; i < args.length; i++) {

      if (args[i].compareToIgnoreCase("-all") == 0)
        includeNonNativeMethods = true;
      else if (args[i].compareToIgnoreCase("-o") == 0) {
        if (args.length > i + 1)
          outputFilename = args[i + 1];
        else {
          throw new CommandLineException("No output filename specified with -o");
        }
      }
      else if (args[i].compareToIgnoreCase("-platform") == 0) {
        if (args.length > i + 1)
          platform = args[i + 1].toUpperCase();
        else {
          throw new CommandLineException("No platform specified with -platform");
        }
      }
      else if (args[i].compareToIgnoreCase("-verbose") == 0) {
        String s = System.getProperty("java.class.path");
        System.err.println("[Search path = " + s + "]");
        verboseOutput = true;
      }
      else if (args[i].compareToIgnoreCase("-version") == 0) {
        displayVersion();
        return false;
      }
      else if (args[i].compareToIgnoreCase("-help") == 0) {
        displayHelp();
        return false;
      }
      else if (args[i].indexOf('-') == 0) {
        throw new CommandLineException("Unknown command line argument: " + args[i]);
      }
    }
    return true;
  }

    /**
     *  This method is the work-horse and calls on the other classes, NativeMethods and
     *  NativeMethod to do the real work.
     */
  public void extractMethods() throws NativeMethodException {

    if (className.length() == 0)
      throw new NativeMethodException("No classname specified");

    if (outputFilename.equals(""))
      outputStream = new java.io.PrintWriter(System.out);
    else {
      try {
        outputStream = new java.io.PrintWriter(new FileWriter(outputFilename));
      }
      catch (java.io.IOException e) {
        System.err.println("Unable to create file: " + outputFilename);
        return;
      }
    }

    try {
      nativeMethods = new NativeMethods(className);
      nativeMethods.setPlatform(platform);
      nativeMethods.setIncludeNonNativeMethods(includeNonNativeMethods);
      nativeMethods.setVerboseOutput(verboseOutput);
      boolean b = nativeMethods.retrieveMethods();
      if (b)
        nativeMethods.emitCode(outputStream);
      outputStream.close();
    }
    catch (Exception e) {
      e.printStackTrace();
    }
  }
}



//*********************************************************************************************************************
//*********************************************************************************************************************
//*********************************************************************************************************************
// NativeMethods
//

  /**
   * NativeMethods represents a vector of all of the methods in a Java class file that are
   * declared with the <b>native</b> modifier. The vector contains references to
   * NativeMethod objects. NativeMethods is a container class.
   */
class NativeMethods extends Vector
{
    /**
     *  The name of the class that this object represents.
     */
  private String className;

    /**
     *  Flag indicating verbose output. Default is <b>false</b>.
     */
  private boolean verboseOutput;

    /**
     *  Flag controlling the method extraction. If true, all methods (not just native) are
     *  retrieved from the class file. Default is <b>false</b>.
     */
  private boolean includeNonNativeMethods;

    /**
     *  The target platform of the generated code (Linux, Win32, or ALL.) Default is ALL.
     */
  private String platform;

    /**
     * Constructs an empty NativeMethods vector and sets default values for fields.
     * <br><br><pre>
     * platform = "ALL"
     * verboseOutput = false;
     * includeNonNativeMethods = false;</pre>
     * @param className  The Java class that is to be processed.
     *
     */
  public NativeMethods(String className) {
    this.className = className;
    platform = "ALL";
    verboseOutput = false;
    includeNonNativeMethods = false;
  }


    /**
     *  Gets the name of the class associated with the methods.
     *  @return The name of the class.
     */
  public String getClassName() {
    return className;
  }

    /**
     *  Sets verboseOutput to value.
     */
  public void setVerboseOutput(boolean value) {
    verboseOutput = value;
  }

    /**
     *  Gets the value of verboseOutput.
     *  @return The value of verboseOutput.
     */
  public boolean getVerboseOutput() {
    return verboseOutput;
  }

    /**
     *  Sets includeNonNativeMethods to value.
     *  Setting this field to true will cause the NativeMethods.retriveMethods() method
     *  to retrieve non-native methods as well. Only native methods are necessary
     *  for JNI programming, but it is sometimes useful to extract other methods
     *  for testing purposes.
     */
  public void setIncludeNonNativeMethods(boolean value) {
    includeNonNativeMethods = value;
  }

    /**
     *  Gets the value of includeNonNativeMethods.
     *  @return The value of includeNonNativeMethods.
     */
  public boolean getIncludeNonNativeMethods() {
    return includeNonNativeMethods;
  }

    /**
     *  Sets the platform. Can be one of three values: LINUX, WIN32, or ALL.
     *  The platform controls code generation. Specifically, these values
     *  will cause the native methods to be tagged with either <b>cdecl</b>,
     *  <b>stdcall</b>, or both, via an {$IFDEF} construct.
     *
     */
  public void setPlatform(String value) throws NativeMethodException {
    String temp = value.toUpperCase();
    if (!value.equals("ALL") && !value.equals("LINUX") && !value.equals("WIN32"))
      throw new NativeMethodException("Unknown platform: " + value);

    platform = temp;
  }

   /**
    *  Gets the value of platform.
    *  @return The value of platform, either LINUX, WIN32, or ALL.
    */
  public String getPlatform() {
    return platform;
  }

   /**
    *  Retrieves all of the methods of the class and adds each one to the vector that
    *  NativeMethods represents.
    *  For each method in the class, three pieces of information are retrieved:
    *  <ol>
    *  <li>The name of the method
    *  <li>The method's type (return type)
    *  <li>The types of parameters
    *  </ol>
    *  @return true, if successful, false, if some kind of exception occurred.
    *
    */
  public boolean retrieveMethods() {
    try {
      Class clazz = ClassLoader.getSystemClassLoader().loadClass(className);
      Method[] methods = clazz.getDeclaredMethods();
      for (int i = 0; i < methods.length; i++) {
        int modifiers = methods[i].getModifiers();
        boolean isNative = java.lang.reflect.Modifier.isNative(modifiers);
        if (isNative || includeNonNativeMethods) {
          NativeMethod nativeMethod = new NativeMethod(this);
          nativeMethod.setPlatform(platform);
          nativeMethod.setName(methods[i].getName());

          String returnType = methods[i].getReturnType().toString();
          String OPReturnType = JNISupport.mapJNITypeToOPType(returnType);
          String javaReturnType = JNISupport.mapJavaTypeToSigType(returnType);

          nativeMethod.setOPReturnType(OPReturnType);
          nativeMethod.setJavaReturnType(javaReturnType);

          if (returnType.equals("void"))
            nativeMethod.isFunction = false;
          else
            nativeMethod.isFunction = true;

          Class[] params = methods[i].getParameterTypes();

          for (int j = 0; j < params.length; j++) {
            String OPType = JNISupport.mapJNITypeToOPType(params[j].getName());
            nativeMethod.addOPParamType(OPType);

            String javaType = JNISupport.mapJavaTypeToSigType(params[j].getName());
            nativeMethod.addJavaSignatureType(javaType);
          }
          add(nativeMethod);
        }
      }
      return true;
    }
    catch (Exception e) {
      e.printStackTrace();
      return false;
    }
  }

    /**
     *  Generates the Delphi method name from the NativeMethod object.
     *  Unless there are overloaded methods in the elements() Enumeration,
     *  this is simply the NativeMethod.getName().
     *  If there are overloads, then "__" and a representation of the
     *  signature are added.
     *
     *  Examples:
     * Signature:  ()V                      Yields: __
     * Signature:  (ZBC)V                   Yields: __ZBC
     * Signature:  ([B)V                    Yields: ___3B
     * Signature:  (Ljava/lang/String;)V    Yields: __Ljava_lang_String_2
     *
     * @param method The NativeMethod whose name is to be obtained
     */
  public String addSigIfNeeded(NativeMethod method)
    throws NativeMethodException
  {
    String name = method.getName();
    //Find out the overload count
    int overload_count = 0;
    Enumeration enum = elements();
    while (enum.hasMoreElements())
    {
      if (name.equals(((NativeMethod) enum.nextElement()).getName())) overload_count += 1;
    }
    if (overload_count < 1)
      throw new NativeMethodException("Overload count error");

    if (overload_count == 1)
      return JNISupport.dollarsToTwentyFours(JNISupport.stretchUnderscores(name, false));

    //overload_count > 1
    //We need JNISignature, but we cannot be in the NativeMethod class
    //(because we need the Enumeration of all native methods), so we
    //had to change the access of getJNISignature().
    String s = method.getJNISignature();    // Something like "(Ljava/lang/String;)V"
    
    //Strip parentheses
    s = s.substring(1, s.indexOf(")")); // Something like Ljava/lang/String;

    name = JNISupport.stretchUnderscores(name, false).concat("__");

    //A zero-length string at this point indicates void input (signature appended is just "__")
    int index = 0;
    String PrimitiveTypes = "ZBCSIJFD";
    while (index < s.length()) {
      char c = s.charAt(index);
      if (PrimitiveTypes.indexOf(c) > 0) {
        name = name.concat(s.substring(0, 1));
        index += 1;
      } else if (s.charAt(index) == 'L') {
        //Even though its a class name we're putting in, it uses the method name representation of underscores
        name = name.concat(JNISupport.slashesAndDotsToUnderscores(JNISupport.stretchUnderscores(s.substring(index, s.indexOf(";", index)), false)) + "_2"); //semi-colon is "_2"
        index = s.indexOf(";", index) + 1;
      } else if (s.charAt(index) == '[') {
        name = name.concat("_3");
        index += 1;
      } else {
        // There should be no other cases
        throw new NativeMethodException("Signature error");
      } //if
    } //while
    return JNISupport.dollarsToTwentyFours(name);
  }

    /**
     *  Generates a Delphi project file (.dpr) containing stub functions for each
     *  of the native methods in the class file.
     *
     * @param outputStream The destination for the output. This can be set to
     * an open file or to System.out to be redirected to a file.
     */
  public void emitCode(PrintWriter outputStream)
    throws NativeMethodException
  {
      // library (note that dollars become plain underscores in this case)
    outputStream.println("library " + JNISupport.dollarsToUnderscores(JNISupport.slashesAndDotsToUnderscores(JNISupport.stretchUnderscores(className, true))) + ";");
    outputStream.println("");

      // uses
    outputStream.println("uses JNI;");
    outputStream.println("");

      // functions/procedures
    Enumeration enum = elements();
    while (enum.hasMoreElements())
      ((NativeMethod) enum.nextElement()).emitCode(className, outputStream);

      // exports
    outputStream.println("exports");
    enum = elements();
    while (enum.hasMoreElements()) {
      NativeMethod method = (NativeMethod) enum.nextElement();
      String methodName = addSigIfNeeded(method);
      //Even though its a class name, it uses the method name representation of underscores
      outputStream.print("  Java_" + JNISupport.dollarsToTwentyFours(JNISupport.slashesAndDotsToUnderscores(JNISupport.stretchUnderscores(className, false))) + "_" + methodName);
      if (enum.hasMoreElements())
        outputStream.println(",");
      else
        outputStream.println(";");
    }

      // end
    outputStream.println("");
    outputStream.println("end.");
  }
}



//*********************************************************************************************************************
//*********************************************************************************************************************
//*********************************************************************************************************************
// NativeMethod
//

  /**
   * A NativeMethod object represents a method in a Java class file that has been declared
   * with the <b>native</b> modifier. A NativeMethod contains information such as the
   * name of the method, the type, and the types of the parameters, if any. A NativeMethod
   * contains a method named emitCode, which generates an Object Pascal representation of
   * the Java method declaration.
   */
class NativeMethod
{
    /**
     *  The NativeMethods object that owns this NativeMethod.
     */
  private NativeMethods owner;

    /**
     *  The name of the method.
     */
  private String name;

    /**
     *  The return type of the method. (Object Pascal type)
     */
  private String OPReturnType = "";

    /**
     *  The return type of the method. (Java type)
     */
  private String javaReturnType = "";

    /**
     *  A vector of the parameters to the method. (Object Pascal types)
     */
  private Vector OPParamTypes;

    /**
     *  A vector of the parameters to the method. (Java signature types)
     */
  private Vector javaSignatureTypes;

    /**
     *  Flag indicating whether the method is an Object Pascal <b>function</b> (true),
     *  or an Object Pascal <b>procedure</b> (false).
     */
  public boolean isFunction;

    /**
     *  The target platform of the generated code (Linux, Win32, or ALL.) Default is ALL.
     */
  private String platform = "ALL";

  /**
   * Creates a new NativeMethod object.
   */
  public NativeMethod(NativeMethods theOwner) {
    owner = theOwner;
    OPParamTypes = new Vector();
    javaSignatureTypes = new Vector();
  }

    /**
     *  Generates an Object Pascal representation of the Java method declaration. The Object Pascal
     *  function/procedure is a stub that contains no executable code.
     *
     * @param className The name of the class that this method belongs to. The name of the
     * function/procedure is determined by the name of the class.
     *
     * @param outputStream The destination for the output. This can be set to
     * an open file or to System.out to be redirected to a file.
     */
  public void emitCode(String className, PrintWriter outputStream)
    throws NativeMethodException
  {

      // Generate header comment
    outputStream.println("(*");
    //Note that dollars are plain underscores, and underscores stretch to "_0005f"
    outputStream.println(" * Class:      " + JNISupport.dollarsToUnderscores(JNISupport.slashesAndDotsToUnderscores(JNISupport.stretchUnderscores(className, true))));
    outputStream.println(" * Method:     " + JNISupport.dollarsToTwentyFours(name));
    outputStream.println(" * Signature:  " + getJNISignature());
    outputStream.println("*)");

    if (isFunction)
      outputStream.print("function ");
    else
      outputStream.print("procedure ");

    //Note that dollars are "_00024", and underscores stretch to "_1" (the exact opposite of the comment!)
    outputStream.println("Java_" + JNISupport.dollarsToTwentyFours(JNISupport.slashesAndDotsToUnderscores(JNISupport.stretchUnderscores(className, false))) + "_" + owner.addSigIfNeeded(this));
    outputStream.print("  (PEnv: PJNIEnv; Obj: JObject");

    Enumeration enum = OPParamTypes.elements();
    int count = 1;
    while (enum.hasMoreElements()) {
      String param = (String) enum.nextElement();
      outputStream.print("; ");
      outputStream.print("Arg" + (count++) + ": " + param.toString());
    }

    outputStream.print(")");
    if (isFunction)
      outputStream.print(": " + OPReturnType);

    if (platform.equals("LINUX"))
      outputStream.println("; cdecl;");
    else if (platform.equals("WIN32"))
      outputStream.println("; stdcall;");
    else //if (platform.equals("ALL"))
      outputStream.println("; {$IFDEF WIN32} stdcall; {$ENDIF} {$IFDEF LINUX} cdecl; {$ENDIF}");

    outputStream.println("begin");
    outputStream.println("end;");
    outputStream.println("");
  }

    /**
     *  Generates a string that represents a Java Native Interface signature.
     *  This is used in the comments that come before each Object Pascal function/procedure
     *  that is generated.
     *  @return A string that represents the signature of the method the object represents,
     *  e.g.:
     *  <pre> "int SomeFunc(long, int, String)" --> "(JILjava/lang/String;)I" </pre>
     */
  protected String getJNISignature() {
    String signature = "(";
    Enumeration enum = javaSignatureTypes.elements();
    while (enum.hasMoreElements())
      signature += enum.nextElement().toString();

    signature += ")";

    if (isFunction)
      signature += javaReturnType;
    else
      signature += "V";

    return signature;
  }

    /**
     *  Gets the name of the NativeMethod object.
     *  @return The name of the method the object represents.
     */
  public String getName() {
    return name;
  }

    /**
     *  Sets the name of the NativeMethod object.
     */
  public void setName(String name) {
    this.name = name;
  }

    /**
     *  Gets the return type (Object Pascal type) of the NativeMethod object.
     *  @return The Object Pascal type (as a String) of the method the object represents.
     */
  public String getOPReturnType() {
    return OPReturnType;
  }

    /**
     *  Sets the return type (Object Pascal type) of the NativeMethod object.
     */
  public void setOPReturnType(String returnType) {
    OPReturnType = returnType;
  }

    /**
     *  Gets the return type (Java type) of the NativeMethod object.
     *  @return The Java type (as a String) of the method that the object represents.
     */
  public String getjavaReturnType() {
    return javaReturnType;
  }

    /**
     *  Sets the return type (Java type) of the NativeMethod object.
     */
  public void setJavaReturnType(String returnType) {
    javaReturnType = returnType;
  }

    /**
     *  Adds a parameter type to the vector of parameters. (Object Pascal type)
     */
  public void addOPParamType(String paramType) {
    OPParamTypes.add(paramType);
  }

    /**
     *  Adds a parameter type to the vector of parameters. (Java type)
     */
  public void addJavaSignatureType(String sigType) {
    javaSignatureTypes.add(sigType);
  }

    /**
     *  Sets the platform. Can be one of three values: LINUX, WIN32, or ALL.
     *  The platform controls code generation. Specifically, these values
     *  will cause the native methods to be tagged with either <b>cdecl</b>,
     *  <b>stdcall</b>, or both, via an {$IFDEF} construct.
     *
     */
  public void setPlatform(String value) throws NativeMethodException {
    String temp = value.toUpperCase();
    if (!temp.equals("ALL") && !temp.equals("LINUX") && !temp.equals("WIN32"))
      throw new NativeMethodException("Unknown platform");

    platform = temp;
  }

    /**
     *  Gets the platform string.
     *  @return The platform, either LINUX, WIN32, or ALL
     */
  public String getPlatform() {
    return platform;
  }

}



//*********************************************************************************************************************
//*********************************************************************************************************************
//*********************************************************************************************************************
// CommandLineException
//

  /**
   *  A CommandLineException is thrown in response to malformed command line arguments.
   */
class CommandLineException extends Exception {
    /**
     * Default constructor for unspecified exceptions.
     */
  public CommandLineException() {
    super("Unspecified exception");
  }

    /**
     * Constructor for specific exceptions.
     *
     * @param message A custom message that describes the type of CommandLineException.
     */
  public CommandLineException(String message) {
    super(message);
  }
}



//*********************************************************************************************************************
//*********************************************************************************************************************
//*********************************************************************************************************************
// NativeMethodException
//

  /**
   *  A NativeMethodException is thrown in response to exceptional circumstances regarding
   *  the processing of native methods within the Java class.
   */
class NativeMethodException extends Exception {
    /**
     * Default constructor for unspecified exceptions.
     */
  public NativeMethodException() {
    super("Unspecified exception");
  }
    /**
     * Constructor for specific exceptions.
     *
     * @param message A custom message that describes the type of NativeMethodException.
     */
  public NativeMethodException(String message) {
    super(message);
  }
}



//*********************************************************************************************************************
//*********************************************************************************************************************
//*********************************************************************************************************************
// JNISupport
//

  /**
   * A support class that contains static methods to help with the generation of Object Pascal code.
   */
class JNISupport
{
   /**
    * Replaces all occurrences of underscores "_" with "_1" (when a method name) or "_0005f" (when class name)
    * @param inputString the String to modify.
    * @param isClassName a boolean specifying whether this is a class name or not.
    * @return The string after the replacements have been made.
    */
  public static String stretchUnderscores(String inputString, boolean isClassName) {
    if (isClassName)
    {
      return inputString.replaceAll("_", "_0005f");
    } else {
      return inputString.replaceAll("_", "_1"); //method name
    }
  }

   /**
    * Replaces all occurrences of slashes "/" and dots (".") with underscores "_".
    * stretchUnderscores should be called before slashesAndDotsToUnderscores.
    * @param inputString the String to modify.
    * @return The string after the replacements have been made.
    */
  public static String slashesAndDotsToUnderscores(String inputString) {
    return inputString.replace('/', '_').replace('.', '_');
  }

   /**
    * Replaces all occurrences of dollars "$" with "_00024".
    * stretchUnderscores and slashesAndDotsToUnderscores should be called before dollarsToTwentyFours.
    * @param inputString the String to modify.
    * @return The string after the replacements have been made.
    */
  public static String dollarsToTwentyFours(String inputString) {
    return inputString.replaceAll("\\$", "_00024");
  }

   /**
    * Replaces all occurrences of dollars "$" with "_".
    * stretchUnderscores and slashesAndDotsToUnderscores should be called before dollarsToUnderscores.
    * @param inputString the String to modify.
    * @return The string after the replacements have been made.
    */
  public static String dollarsToUnderscores(String inputString) {
    return inputString.replaceAll("\\$", "_");
  }

   /**
    *  Given a string representing a JNI type, return the equivalent Object Pascal type.
    *  e.g:
    *  <pre>  "int" -> "JInt"
    *  "[D" -> "JDoubleArray" </pre>
    *
    * @param JNIType the String to modify.
    * @return A string representing the Object Pascal type.
    */
  public static String mapJNITypeToOPType(String JNIType) {

    String OPType;

      // Some types start with "class "
    int index = JNIType.indexOf(" ");
    JNIType = JNIType.substring(index+1);

    if (JNIType.equals("void"))
      OPType = "void";
    else if (JNIType.equals("int"))
      OPType = "JInt";
    else if (JNIType.equals("double"))
      OPType = "JDouble";
    else if (JNIType.equals("float"))
      OPType = "JFloat";
    else if (JNIType.equals("char"))
      OPType = "JChar";
    else if (JNIType.equals("short"))
      OPType = "JShort";
    else if (JNIType.equals("boolean"))
      OPType = "JBoolean";
    else if (JNIType.equals("byte"))
      OPType = "JByte";
    else if (JNIType.equals("long"))
      OPType = "JLong";
    else if (JNIType.equals("java.lang.String"))
      OPType = "JString";
    else if (JNIType.equals("[Z"))
      OPType = "JBooleanArray";
    else if (JNIType.equals("[B"))
      OPType = "JByteArray";
    else if (JNIType.equals("[C"))
      OPType = "JCharArray";
    else if (JNIType.equals("[S"))
      OPType = "JShortArray";
    else if (JNIType.equals("[I"))
      OPType = "JIntArray";
    else if (JNIType.equals("[J"))
      OPType = "JLongArray";
    else if (JNIType.equals("[F"))
      OPType = "JFloatArray";
    else if (JNIType.equals("[D"))
      OPType = "JDoubleArray";
    else if (JNIType.substring(0, 2).equals("[L"))
      OPType = "JObjectArray";
    else if (JNIType.substring(0, 2).equals("[["))
      OPType = "JObjectArray";
    else if (JNIType.equals("java.lang.Class"))
      OPType = "JClass";

      // the next two can be handled in the else if we like
    else if (JNIType.equals("java.lang.Object"))
      OPType = "JObject";
    else if (JNIType.substring(0, 1).equals("L"))
      OPType = "JObject";

    else
      OPType = "JObject";

    return OPType;
  }

   /**
    *  Given a string representing a Java type, return the equivalent signature type.
    *  e.g.:
    *  <pre>  "void" -> "V"
    *  "java.lang.String" -> "Ljava/lang/String;" </pre>
    *
    * @param javaType the String to modify.
    * @return A string representing the Java type.
    */
  public static String mapJavaTypeToSigType(String javaType) {

    String sigType;

      // Some types start with "class "
    int index = javaType.indexOf(" ");
    javaType = javaType.substring(index + 1);

    if (javaType.equals("void"))
      sigType = "V";
    else if (javaType.equals("int"))
      sigType = "I";
    else if (javaType.equals("double"))
      sigType = "D";
    else if (javaType.equals("float"))
      sigType = "F";
    else if (javaType.equals("char"))
      sigType = "C";
    else if (javaType.equals("short"))
      sigType = "S";
    else if (javaType.equals("boolean"))
      sigType = "Z";
    else if (javaType.equals("byte"))
      sigType = "B";
    else if (javaType.equals("long"))
      sigType = "J";

    else {
      sigType = javaType.replace('.', '/');

        // If it's not an array, it's an object, so mark it up
      if (sigType.indexOf("[") != 0)
        sigType = "L" + sigType + ';';
    }
    return sigType;
  }
}