{******************************************************************}
{                                                                  }
{  javadpr - Generates a Delphi project file from a Java class     }
{            file.                                                 }
{                                                                  }
{  javadpr produces an Object Pascal file from a Java class. This  }
{  file forms the basis that allow Java and Delphi-produced code   }
{  to interact via the Java Native Interface.                      }
{                                                                  }
{  This tool is similar to the javah tool from Sun Microsystems,   }
{  Inc., which produces C header files from Java class files.      }
{                                                                  }
{ Copyright (C) 2001 MMG and Associates                            }
{ www.pacifier.com/~mmead/jni/delphi                               }
{                                                                  }
{ 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:                                                         }
{   29 Apr 2001 - Created.                                         }
{                                                                  }
{******************************************************************}
program javadpr;

{$APPTYPE CONSOLE}

uses
  SysUtils, Classes, Contnrs, Windows,
  JNI;

const
  VERSION_STRING = '1.0.0';

type

  TNativeMethods = class(TObjectList)
    public
      JavaClass: string;
      constructor Create(const JavaClassName: string);
      procedure EmitCode(const JavaClass: string; const Stream: TextFile);
  end;

  TNativeMethod = class(TObject)
    public
      Name: string;
      DelphiReturnType: string;
      JavaReturnType: string;
      PackageName: string;
      JavaSignatureTypes: TStringList;
      DelphiParamTypes: TStringList;
      constructor Create;
      destructor Destroy; override;
      procedure EmitCode(const JavaClass: string; const Stream: TextFile);
      function JNISignature: string;
      function JavaSignature: string;
  end;

var
  JNIEnv: TJNIEnv;
  NativeClassCls: JClass;
  NativeClassObj: JObject;
  NativeMethods: TNativeMethods;
  JavaClassFile: string;
  OutputFilename: string;
  OutputFile: Text;
  ClassPath: string;
  IncludeNonNativeMethods: Boolean;
  VerboseOutput: Boolean;
  Platform: string;


//=====================================================================================
// Support functions

function GetEnvironmentString(const Source : string) : string;
const
  BUFSIZE = 2000;
var
  buffer, variable : PChar;
  value : string;
begin
  buffer := StrAlloc(BUFSIZE);
  variable := StrAlloc(Length(Source) + 1);
  StrPCopy(variable, Source);
  GetEnvironmentVariable(variable, buffer, BUFSIZE);
  value := StrPas(buffer);
  StrDispose(buffer);
  StrDispose(variable);
  Result := value;
end;

function DotsToUnderscores(const Text: string): string;
begin
  Result := StringReplace(Text, '.', '_', [rfReplaceAll]);
end;

function SlashesToDots(const Text: string): string;
begin
  Result := StringReplace(Text, '/', '.', [rfReplaceAll]);
end;

procedure DisplayVersion;
begin
  Writeln('javadpr version "', VERSION_STRING, '"');
end;

procedure DisplayError(const Text: string);
begin
  Writeln(Text, '  Try -help.');
end;

procedure DisplayHelp;
begin
  Writeln('Usage: javadpr [options] class');
  Writeln;
  Writeln('where [options] include:');
  Writeln;
  Writeln('        -help                 Print this help message');
  Writeln('        -classpath <path>     Path from which to load classes');
  Writeln('        -o <file>             Output file (default is stdout)');
  Writeln('        -version              Print version information then quit');
  Writeln('        -verbose              Enable verbose output');
  Writeln('        -all                  Include non-native methods (internal debugging)');
  Writeln('        -platform <platform>  For code generation (Linux|Win32|All)');
  Writeln;
  Writeln('<classes> are specified with their fully qualified names (for instance, java.awt.Rectangle).');
  Writeln;
end;

{***************************** ParseCommandLine ***********************************
s  Parse the command line to get the user's options. Quick and dirty, needs work.
***************************** ParseCommandLine ***********************************}
function ParseCommandLine: Boolean;
var
  I: Integer;
  LastCommand: string;
begin
  Result := False;
  if ParamCount = 0 then begin
    DisplayHelp;
    Exit;
  end;

    // The class file must be the last parameter
  LastCommand := ParamStr(ParamCount);
  if LastCommand[1] <> '-' then
    JavaClassFile := LastCommand;

  for I := 1 to ParamCount do begin

    if CompareText(ParamStr(I), '-classpath') = 0 then begin
      if ParamCount >= I + 1 then
        ClassPath := ParamStr(I + 1)
      else begin
        DisplayError('No classpath specified.');
        Exit;
      end;
    end
    else if CompareText(ParamStr(I), '-all') = 0 then begin
      IncludeNonNativeMethods := True;
    end
    else if CompareText(ParamStr(I), '-help') = 0 then begin
      DisplayHelp;
      Exit;
    end
    else if CompareText(ParamStr(I), '-version') = 0 then begin
      DisplayVersion;
      Exit;
    end
    else if CompareText(ParamStr(I), '-verbose') = 0 then begin
      VerboseOutput := True;
    end
    else if CompareText(ParamStr(I), '-o') = 0 then begin
      if ParamCount >= I + 1 then
        OutputFilename := ParamStr(I + 1)
      else begin
        DisplayError('No output file specified.');
        Exit;
      end;
    end
    else if CompareText(ParamStr(I), '-platform') = 0 then begin
      if ParamCount >= I + 1 then
        Platform := UpperCase(ParamStr(I + 1))
      else begin
        DisplayError('No platform specified.');
        Exit;
      end;
      if (Platform <> 'LINUX') and (Platform <> 'WIN32') and (Platform <> 'ALL') then begin
        DisplayError('Unknown platform specified.');
        Exit;
      end;
    end
      // Unknown switch
    else if ParamStr(I)[1] = '-' then begin
      DisplayHelp;
      Exit;
    end;

  end;
  Result := True;
end;

{***************************** JNITypeToOPType ***********************************
  Maps a JNI type to Object Pascal type.

  e.g.:

    int -> JInt
    [B -> JByteArray
    java.lang.Object -> JObject

***************************** JNITypeToOPType ***********************************}
function JNITypeToOPType(const StrType: string): string;
var
  Position: Integer;
  JType: string;
begin

    // Some types have "class" prepended
  Position := Pos(' ', StrType);
  JType := Copy(StrType, Position + 1, Length(StrType));

  if (JType = 'void') then
    Result := 'void' // just a placeholder
  else if (JType = 'int') then
    Result := 'JInt'
  else if (JType = 'double') then
    Result := 'JDouble'
  else if (JType = 'float') then
    Result := 'JFloat'
  else if (JType = 'char') then
    Result := 'JChar'
  else if (JType = 'short') then
    Result := 'JShort'
  else if (JType = 'boolean') then
    Result := 'JBoolean'
  else if (JType = 'byte') then
    Result := 'JByte'
  else if (JType = 'long') then
    Result := 'JLong'
  else if (JType = 'java.lang.String') then
    Result := 'JString'
  else if (JType = '[Z') then
    Result := 'JBooleanArray'
  else if (JType = '[B') then
    Result := 'JByteArray'
  else if (JType = '[C') then
    Result := 'JCharArray'
  else if (JType = '[S') then
    Result := 'JShortArray'
  else if (JType = '[I') then
    Result := 'JIntArray'
  else if (JType = '[J') then
    Result := 'JLongArray'
  else if (JType = '[F') then
    Result := 'JFloatArray'
  else if (JType = '[D') then
    Result := 'JDoubleArray'
  else if Copy(JType, 1, 2) = '[L' then
    Result := 'JObjectArray'
  else if Copy(JType, 1, 2) = '[[' then
    Result := 'JObjectArray'
  else if (JType = 'java.lang.Class') then
    Result := 'JClass'

    // the next two can be handled in the else if you like
  else if (JType = 'java.lang.Object') then
    Result := 'JObject'
  else if Copy(JType, 1, 1) = 'L' then
    Result := 'JObject'

  else
    Result := 'JObject'; // the rest must be derived from JObject

end;

{***************************** JNITypeToJavaType ***********************************
  Maps a JNI type into a Java type. Still in development.

  e.g.:

    V -> void
    I -> int
    [B -> byte[]

***************************** JNITypeToJavaType ***********************************}
function JNITypeToJavaType(const StrType: string): string;
var
  I, Position: Integer;
  JType: string;

  function LetterToType(const Letter: string): string;
  begin
    if (Letter = 'V') then
      Result := 'void'
    else if (Letter = 'I') then
      Result := 'int'
    else if (Letter = 'D') then
      Result := 'double'
    else if (Letter = 'F') then
      Result := 'float'
    else if (Letter = 'C') then
      Result := 'char'
    else if (Letter = 'S') then
      Result := 'short'
    else if (Letter = 'Z') then
      Result := 'boolean'
    else if (Letter = 'B') then
      Result := 'byte'
    else if (Letter = 'J') then
      Result := 'long'
    else
      Result := '';
  end;


begin

    // Some types have "class" prepended
  Position := Pos(' ', StrType);
  JType := Copy(StrType, Position + 1, Length(StrType));

  Result := LetterToType(JType[1]);
  if Result <> '' then
    Exit
  else if (JType[1] = 'L') then
    Result := SlashesToDots(Copy(JType, 2, Length(JType) - 2))
  else if (JType[1] = '[') then begin
    Position := LastDelimiter('[', JType);
    Result := LetterToType(JType[Position + 1]);
    if Result = '' then
      Result := Copy(JType, Position + 2, Length(JType) - Position - 2);
    for I := 1 to Position do
      Result := Result + '[]';

    Result := SlashesToDots(Result);
  end
  else
    Result := '****UNKNOWN****';

end;


{***************************** JavaTypeToSigType ***********************************
  Maps a Java type into a signature.

  e.g.:

    int -> I
    [B -> [B
    Ljava.lang.String -> Ljava/lang/String;
***************************** JavaTypeToSigType ***********************************}
function JavaTypeToSigType(const StrType: string): string;
var
  Position: Integer;
  JType: string;
begin

    // Some types have "class" prepended
  Position := Pos(' ', StrType);
  JType := Copy(StrType, Position + 1, Length(StrType));

  if (JType = 'void') then
    Result := 'V'
  else if (JType = 'int') then
    Result := 'I'
  else if (JType = 'double') then
    Result := 'D'
  else if (JType = 'float') then
    Result := 'F'
  else if (JType = 'char') then
    Result := 'C'
  else if (JType = 'short') then
    Result := 'S'
  else if (JType = 'boolean') then
    Result := 'Z'
  else if (JType = 'byte') then
    Result := 'B'
  else if (JType = 'long') then
    Result := 'J'

  else begin
    Result := JType; //type;
    Result := StringReplace(Result, '.', '/', [rfReplaceAll]);

      // If it's not an array
    if (Result[1] <> '[') then
      Result := 'L' + Result + ';';
  end;

end;

{***************************** GetNativeMethods ***********************************
  This does all of the work. The equivalent Java code is significantly shorter.
  This is due to the overhead involved in locating classes and methods and invoking
  methods.
***************************** GetNativeMethods ***********************************}
function GetNativeMethods(const JavaClassName: string): TNativeMethods;
var
  NativeMethod: TNativeMethod;
  MID: JMethodID;
  ClassLoaderCls: JClass;
  ClassLoaderInst: JObject;
  JNIMethods, JNIParams: JArray;
  JNIMethod, JNIParam: JObject;
  I, J, Length, Count: Integer;
  theClass, Cls: JClass;
  ClassObj: JObject;
  JStr: JString;
  Str, JType: string;
  ModifierCls: JClass;
  IsNativeMethod: JMethodID;
  IsNative: JBoolean;
  Modifiers: Integer;
begin

    // Get the isNative method in the java.lang.reflect.Modifier class so we can
    // test all the methods to find out if they are native
  ModifierCls := JNIEnv.FindClass('java/lang/reflect/Modifier');
  IsNativeMethod := JNIEnv.GetStaticMethodID(ModifierCls, 'isNative', '(I)Z');

    //***********************************************************************************************
    // Find and load the ClassLoader
    // We need to use the system loader to load the class we want to process
  ClassLoaderCls := JNIEnv.FindClass('java/lang/ClassLoader');
  if ClassLoaderCls = nil then
    raise Exception.Create('FindClass failed: java/lang/ClassLoader');

    // Get the method to load the system class loader
  MID := JNIEnv.GetStaticMethodID(ClassLoaderCls, 'getSystemClassLoader', '()Ljava/lang/ClassLoader;');
  if MID = nil then
    raise Exception.Create('GetStaticMethodID failed: getSystemClassLoader');

    // Get the loader instance
  ClassLoaderInst := JNIEnv.CallStaticObjectMethod(ClassLoaderCls, Mid, []);
  if ClassLoaderInst = nil then
    raise Exception.Create('CallStaticObjectMethod failed: getSystemClassLoader');

    //***********************************************************************************************
    // Get method used to load the class
  MID := JNIEnv.GetMethodID(ClassLoaderCls, 'loadClass', '(Ljava/lang/String;)Ljava/lang/Class;');
  if MID = nil then
    raise Exception.CreateFmt('GetMethodID failed: loadClass', []);

    // Load the class
  NativeClassObj := JNIEnv.CallObjectMethod(ClassLoaderInst, Mid, [JavaClassName]);
  if NativeClassObj = nil then
    raise Exception.CreateFmt('CallObjectMethod failed: loadClass(%s)', [JavaClassName]);

    // Get the class object's class
  NativeClassCls := JNIEnv.GetObjectClass(NativeClassObj);
  if NativeClassCls = nil then
    raise Exception.CreateFmt('GetObjectClass failed for %s', [JavaClassName]);


    //***********************************************************************************************
    // Get method to retrieve declared methods
  MID := JNIEnv.GetMethodID(NativeClassCls, 'getDeclaredMethods', '()[Ljava/lang/reflect/Method;');
  if Mid = nil then
    raise Exception.CreateFmt('GetMethodID failed: %s.getDeclaredMethods', [JavaClassName]);

    // Get all methods
  JNIMethods := JNIEnv.CallObjectMethod(NativeClassObj, Mid, []);
  if JNIMethods = nil then
    raise Exception.CreateFmt('CallObjectMethod failed: %s.getDeclaredMethods', [JavaClassName]);

    // This will hold the parsed methods (name, parameters, return type, etc.)
  Result := TNativeMethods.Create(JavaClassName);

    // Process each method in the class
  Length := JNIEnv.GetArrayLength(JNIMethods);
  for I := 0 to Length - 1 do begin

      // Next method
    JNIMethod := JNIEnv.GetObjectArrayElement(JNIMethods, I);
    if JNIMethod = nil then
      raise Exception.Create('GetObjectArrayElement failed for JNIMethods');

    theClass := JNIEnv.GetObjectClass(JNIMethod);
    if theClass = nil then
      raise Exception.Create('GetObjectClass failed for array element JNIMethod');


      // If -all switch was not used, only get native methods
    if not IncludeNonNativeMethods then begin

        //***********************************************************************************************
        // Only process methods with the 'native' modifier
      Mid := JNIEnv.GetMethodID(theClass, 'getModifiers', '()I');
      if Mid = nil then
        raise Exception.Create('GetMethodID failed: getModifiers');

      Modifiers := JNIEnv.CallIntMethod(JNIMethod, Mid, []);

      IsNative := JNIEnv.CallStaticBooleanMethod(ModifierCls, IsNativeMethod, [Modifiers]);

      if not IsNative then
        Continue;

    end;

      // We have a native method, so create our object to hold it
    NativeMethod := TNativeMethod.Create;


      //***********************************************************************************************
      // Get the method's name
    Mid := JNIEnv.GetMethodID(theClass, 'getName', '()Ljava/lang/String;');
    if Mid = nil then
      raise Exception.Create('GetMethodID failed: getName on JNIMethod');

    JStr := JNIEnv.CallObjectMethod(JNIMethod, Mid, []);
    NativeMethod.Name := JNIEnv.JStringToString(JStr);


      //***********************************************************************************************
      // Get the method's return type (as a string)
    Mid := JNIEnv.GetMethodID(theClass, 'getReturnType', '()Ljava/lang/Class;');
    if Mid = nil then
      raise Exception.Create('GetMethodID failed: getReturnType');

    ClassObj := JNIEnv.CallObjectMethod(JNIMethod, Mid, []);
    if Mid = nil then
      raise Exception.Create('CallObjectMethod failed for JNIMethod');

    Cls := JNIEnv.GetObjectClass(ClassObj);
    if Cls = nil then
      raise Exception.Create('GetObjectClass failed on JNIMethod');

    Mid := JNIEnv.GetMethodID(Cls, 'toString', '()Ljava/lang/String;');
    if Mid = nil then
      raise Exception.Create('GetMethodID failed: toString');

    JStr := JNIEnv.CallObjectMethod(ClassObj, Mid, []);
    Str := JNIEnv.JStringToString(JStr);

      // e.g. int -> I
    NativeMethod.JavaReturnType := JavaTypeToSigType(Str);

      // e.g. int -> JInt
    NativeMethod.DelphiReturnType := JNITypeToOPType(Str);


      //***********************************************************************************************
      // Get method to retrieve parameter types
    Mid := JNIEnv.GetMethodID(theClass, 'getParameterTypes', '()[Ljava/lang/Class;');
    if Mid = nil then
      raise Exception.Create('GetMethodID failed: getParameterTypes');

       // Get all parameters (types only, no names)
    JNIParams := JNIEnv.CallObjectMethod(JNIMethod, Mid, []);
    if JNIParams = nil then
      raise Exception.Create('CallObjectMethod failed: getParameterTypes');

      // Process each parameter
    Count := JNIEnv.GetArrayLength(JNIParams);
    for J := 0 to Count - 1 do begin

        // Next parameter
      JNIParam := JNIEnv.GetObjectArrayElement(JNIParams, J);
      if JNIParam = nil then
        raise Exception.Create('GetObjectArrayElement failed for JNIParams');

      Cls := JNIEnv.GetObjectClass(JNIParam);
      if Cls = nil then
        raise Exception.Create('GetObjectClass failed on JNIParam');

        // The name is actually the type (e.g. int or java.lang.String)
      Mid := JNIEnv.GetMethodID(Cls, 'getName', '()Ljava/lang/String;');
      if Mid = nil then
        raise Exception.Create('GetMethodID failed: getName on JNIParam');

        // Get the type (as string)
      JStr := JNIEnv.CallObjectMethod(JNIParam, Mid, []);
      JType := JNIEnv.JStringToString(JStr);

        // Save Java type (e.g. int -> I)
      Str := JavaTypeToSigType(JType);
      NativeMethod.JavaSignatureTypes.Add(Str);

        // Save Delphi type (e.g. int -> JInt)
      Str := JNITypeToOPType(JType);
      NativeMethod.DelphiParamTypes.Add(Str);
    end;

      // Add this method to list
    Result.Add(NativeMethod);

  end;
end;
// End support functions
//=====================================================================================



//=====================================================================================
{ TNativeMethod }

constructor TNativeMethod.Create;
begin
  JavaSignatureTypes := TStringList.Create;
  DelphiParamTypes := TStringList.Create;
end;

destructor TNativeMethod.Destroy;
begin
  JavaSignatureTypes.Free;
  DelphiParamTypes.Free;
end;


{******************* TNativeMethod.EmitCode *************************
  Generates the code for this method and sends the output to
  Stream.
******************* TNativeMethod.EmitCode *************************}
procedure TNativeMethod.EmitCode(const JavaClass: string; const Stream: TextFile);
var
  Param: string;
  I, Count: Integer;
  CallingConvention: string;
begin

    // Generate header comment
  Writeln(Stream, '(*');
  Writeln(Stream, ' * Class:     ', DotsToUnderscores(JavaClass));
  Writeln(Stream, ' * Method:    ', Name);
  Writeln(Stream, ' * Signature: ', JNISignature);
  Writeln(Stream, ' *)');

    // Procedure/function
  if DelphiReturnType = 'void' then
    Write(Stream, 'procedure ')
  else
    Write(Stream, 'function ');

    // Method name and required parameters
  Write(Stream, Format('Java_%s_%s', [DotsToUnderscores(JavaClass), Name]));

    // Parameters (first two are required and always the same)
  Write(Stream, '(PEnv: PJNIEnv; Obj: JObject');
  Count := DelphiParamTypes.Count;
  for I := 0 to Count - 1 do begin
    Param := Format('; Arg%d: %s', [I + 1, DelphiParamTypes[I]]);
    Write(Stream, Param);
  end;
  Write(Stream, ')');

    // Calling convention
  if Platform = 'LINUX' then
    CallingConvention := '; cdecl;'
  else if Platform = 'WIN32' then
    CallingConvention := '; stdcall;'
  else
    CallingConvention := '; {$IFDEF WIN32} stdcall; {$ENDIF} {$IFDEF LINUX} cdecl; {$ENDIF}';

    // Return type
  if DelphiReturnType = 'void' then
    Writeln(Stream, CallingConvention)
  else
    Writeln(Stream, ': ', DelphiReturnType, CallingConvention);

    // Empty method (to be implemented by programmer)
  Writeln(Stream, 'begin');
  Writeln(Stream, 'end;');
  Writeln(Stream);

end;

{******************* TNativeMethod.JavaSignature *************************
  Constructs a Java method signature from the JNI signatures of each
  method parameter.

  e.g.:

    (J)[I
    long SomeFunction(int[])

    ([Ljava/lang/String;)Ljava/lang/String;
    java.lang.String Function(java.lang.String[])

******************* TNativeMethod.JavaSignature *************************}
function TNativeMethod.JavaSignature: string;
var
  I, Count: Integer;
  JType: string;
begin
  Result := JNITypeToJavaType(JavaReturnType) + ' ' + Name + '(';
  Count := JavaSignatureTypes.Count;
  for I := 0 to Count - 1 do begin
    if I > 0 then
      Result := Result + ', ';
      JType := JNITypeToJavaType(JavaSignatureTypes[I]);
    Result := Result + Format('%s', [JType]);
  end;
  Result := Result + ')';

end;

{******************* TNativeMethod.JNISignature *************************
  Constructs the complete JNI method signature from the JNI signature
  of each parameter.

  e.g.:

    (IIJ)[Ljava/lang/String;

    The signature above describes a method takes 3 parameters,
    2 integers and a long, and returns an array of Java Strings.

******************* TNativeMethod.JNISignature *************************}
function TNativeMethod.JNISignature: string;
var
  I, Count: Integer;
begin
  Result := '(';
  Count := JavaSignatureTypes.Count;
  for I := 0 to Count - 1 do begin
    Result := Result + Format('%s', [JavaSignatureTypes[I]]);
  end;
  Result := Result + ')';

  if JavaReturnType = 'void' then
    Result := Result + 'V'
  else
    Result := Result + JavaReturnType;

end;
// End TNativeMethod
//=====================================================================================



//=====================================================================================
{ TNativeMethods }

constructor TNativeMethods.Create(const JavaClassName: string);
begin
  JavaClass := JavaClassName;
end;

{******************* TNativeMethods.EmitCode *************************
  Generates the code for the Delphi project. Calls the EmitCode
  method of each NativeMethod object to generate the code for each
  individual method.
******************* TNativeMethods.EmitCode *************************}
procedure TNativeMethods.EmitCode(const JavaClass: string; const Stream: TextFile);
var
  I: Integer;
  NativeMethod: TNativeMethod;
begin

    // Generate module name and uses clause. e.g.:
    //
    //  library JNITest;
    //
    //  uses JNI;
    //
  Writeln(Stream, 'library ', DotsToUnderscores(JavaClass), ';');
  Writeln(Stream);
  Writeln(Stream, 'uses JNI;');
  Writeln(Stream);

    // Call each NativeMethod object to output the function info
  for I := 0 to Count - 1 do
    TNativeMethod(Items[I]).EmitCode(JavaClass, Stream);

    // Generate exports section
  Writeln(Stream, 'exports');
  for I := 0 to Count - 1 do begin
    NativeMethod := TNativeMethod(Items[I]);
    Write(Stream, Format('  Java_%s_%s', [DotsToUnderscores(JavaClass), NativeMethod.Name]));
    if I < Count - 1 then
      Writeln(Stream, ',')
    else
      Writeln(Stream, ';');

  end;
  Writeln(Stream);

    // End of file
  Writeln(Stream, 'end.');

end;
// End TNativeMethods
//=====================================================================================



//=====================================================================================
// Main program

var
  vm_args11: JDK1_1InitArgs;
  EnvClassPath: string;
  Options: array [0..4] of JavaVMOption;
  VM_args: JavaVMInitArgs;
  JavaVM: TJavaVM;
  Errcode: Integer;
begin

  JavaClassFile := '';
  ClassPath := '';
  IncludeNonNativeMethods := False;
  VerboseOutput := False;
  Platform := 'ALL';

  {$IFDEF WIN32}
    Platform := 'WIN32';
  {$ENDIF}

  {$IFDEF LINUX}
    Platform := 'LINUX';
  {$ENDIF}

  try

    if not ParseCommandLine then
      Exit;

    EnvClassPath := GetEnvironmentString('CLASSPATH');

    if VerboseOutput then begin
      JNI_GetDefaultJavaVMInitArgs(@vm_args11);
      Write('[Search path = ');
      if vm_args11.classpath <> nil then
        Write(vm_args11.classpath);

      if EnvClassPath <> '' then
        Write(';', EnvClassPath);

      Writeln(']');
    end;

      // Create the JVM (using a wrapper class)
    JavaVM := TJavaVM.Create;

      // Set the options for the VM
    ClassPath := '-Djava.class.path=' + ClassPath + ';' + EnvClassPath;
    Options[0].optionString := PChar(ClassPath);
    VM_args.version := JNI_VERSION_1_2;
    VM_args.options := @Options;
    VM_args.nOptions := 1;

      // Load the VM
    Errcode := JavaVM.LoadVM(VM_args);
    if Errcode < 0 then begin
      WriteLn(Format('Error loading JavaVM, error code = %d', [Errcode]));
      Exit;
    end;

      // Create a Java environment from the JVM's Env (another wrapper class)
    JNIEnv := TJNIEnv.Create(JavaVM.Env);

    if JavaClassFile = '' then begin
      Writeln('Error: No classes were specified on the command line.  Try -help.');
      Exit;
    end;

      // This does all of the JNI work of retrieving the class methods
    NativeMethods := GetNativeMethods(JavaClassFile);

      // -o <file> was used
    if OutputFilename <> '' then begin
      AssignFile(OutputFile, OutputFilename);
      Rewrite(OutputFile);

      if VerboseOutput then
        Writeln('Creating file ', OutputFilename, '...');

      NativeMethods.EmitCode(JavaClassFile, OutputFile);
      Close(OutputFile);

      if VerboseOutput then
        Writeln('Done.');
    end
    else
      NativeMethods.EmitCode(JavaClassFile, Output);

  except
    on E : Exception do
      WriteLn('Error: ' + E.Message);
  end;

end.