//==================================================================================================
// J a v a                                                                                Interface
// V i r t u a l _ m a c h i n e
//                                                                                By Bruno Bachelet
//==================================================================================================
// Copyright (c) 1999-2016
// Bruno Bachelet - bruno@nawouak.net - http://www.nawouak.net
//
// This file is part of the B++ Library. This library is free software; you can redistribute it
// and/or modify it under the terms of the GNU Library General Public License as published by the
// Free Software Foundation; either version 2 of the License, or (at your option) any later
// version.
//
// This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
// the GNU Library General Public License for more details (http://www.gnu.org).

/*DESCRIPTION*/
/* This module provides a class to manipulate the Java Virtual Machine (JVM). */

// File Name //-------------------------------------------------------------------------------------
#line __LINE__ "java/virtual_machine.hpp"

// Guardian //--------------------------------------------------------------------------------------
#ifndef guJavaVirtualMachine
#define guJavaVirtualMachine

// Headers //---------------------------------------------------------------------------------------
#include <bpp/java/jni.hpp> /*INCLUDE*/

namespace bpp {

// Importation/Exportation //-----------------------------------------------------------------------
#ifdef JAVA_DLL
 #define dll_export DLL_EXPORT
#else
 #define dll_export DLL_IMPORT
#endif

// Namespaces //------------------------------------------------------------------------------------
#define public_area  javaVirtualMachine
#define private_area javaVirtualMachine_private

namespace public_area  { /*NAMESPACE*/ using namespace javaJni; }
namespace private_area { using namespace public_area; }

extern_module_name;

// Initialization //--------------------------------------------------------------------------------
#define iniJavaVirtualMachine
has_initializer;

// Macrocommands //---------------------------------------------------------------------------------

// Types & Classes //-------------------------------------------------------------------------------
namespace public_area {
 //------------------------------------------------------------------------------------------Classes
 class clVirtualMachine;
 //-----------------------------------------------------------------------------------Constant Types
 typedef const clVirtualMachine ctVirtualMachine;
}

namespace private_area {
 #ifdef JNI_VERSION_1_2
  typedef jyInteger (*tyCreateVMFunction)(jyVirtualMachine *,jyContext *,JavaVMInitArgs *);
 #else
  typedef jyInteger (*tyCreateVMFunction)(jyVirtualMachine *,jyContext *,JDK1_1InitArgs *);
 #endif
}

// Functions Interface //---------------------------------------------------------------------------
namespace public_area {
 function void               getVirtualMachine(jyContext);
 function void               loadVirtualMachine(void);
 function void               unloadVirtualMachine(void);
 function ctVirtualMachine & virtualMachine(void);
}

namespace private_area { void loadVirtualMachine(void); }

// Errors //----------------------------------------------------------------------------------------
namespace public_area {
 /*ERROR*/ extern_error erClassPathParameter; /* The 'class_path' parameter is missing. */

 /*ERROR*/ extern_error erJavaMaximumHeapParameter;
 /* The 'java_maximum_heap_size' parameter is missing. */

 /*ERROR*/ extern_error erJavaMinimumHeapParameter;
 /* The 'java_minimum_heap_size' parameter is missing. */

 /*ERROR*/ extern_error erJavaThreadAttaching;
 /* Can not attach the thread to the Java Virtual Machine. */

 /*ERROR*/ extern_error erJavaThreadDetaching;
 /* Can not detach the thread from the Java Virtual Machine. */

 /*ERROR*/ extern_error erVirtualMachineCreation; /* Can not create the Java Virtual Machine. */

 /*ERROR*/ extern_error erVirtualMachineLocationParameter;
 /* The 'java_virtual_machine_location' parameter is missing. */

 /*ERROR*/ extern_error erVirtualMachineNotLoaded; /* The Java Virtual Machine is not loaded. */
}

// Constants & Variables //-------------------------------------------------------------------------
extern_static_constant(private,tyString,goClassPath,?);
extern_static_constant(private,tyCardinal,goMaxHeapSize,?);
extern_static_constant(private,tyCardinal,goMinHeapSize,?);
extern_dynamic_constant(private,clString,goDummyLocationFlag,?);
extern_dynamic_constant(private,clVirtualMachine,goVirtualMachine,?);
extern_static_constant(private,clMutex,goMutex,?);

namespace private_area { extern function tyCreateVMFunction createVirtualMachine; }

// V i r t u a l M a c h i n e  Interface //--------------------------------------------------------
namespace public_area {
 /*CLASS clVirtualMachine */ /* Represents a Java Virtual Machine. */
 class clVirtualMachine {
  //-----------------------------------------------------------------------------------------Private
  private_property tyBoolean atEmbedded;

  private_property clVirtualMachine & operator = (ctVirtualMachine &);
  private_property constructor clVirtualMachine(ctVirtualMachine &);
  //------------------------------------------------------------------------------------------Public
  /*ATTRIBUTE clVirtualMachine */ /* Pointer to the virtual machine in JNI. */
  read_only_attribute(jyVirtualMachine,atJniPointer,jniPointer);

  public_property constructor clVirtualMachine(void);
  public_property constructor clVirtualMachine(jyContext);
  public_property destructor clVirtualMachine(void);

  public_property void      attachThread(void) const;
  public_property jyContext context(void) const;
  public_property void      detachThread(void) const;
 };
}

// Functions Inline //------------------------------------------------------------------------------
namespace public_area  {}
namespace private_area {}

// End //-------------------------------------------------------------------------------------------
}
#undef dll_export
#undef public_area
#undef private_area
#endif
 
//==================================================================================================
// J a v a                                                                           Implementation
// V i r t u a l _ m a c h i n e
//                                                                                By Bruno Bachelet
//==================================================================================================
// Copyright (c) 1999-2016
// Bruno Bachelet - bruno@nawouak.net - http://www.nawouak.net
//
// This file is part of the B++ Library. This library is free software; you can redistribute it
// and/or modify it under the terms of the GNU Library General Public License as published by the
// Free Software Foundation; either version 2 of the License, or (at your option) any later
// version.
//
// This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
// the GNU Library General Public License for more details (http://www.gnu.org).

// File Name //-------------------------------------------------------------------------------------
#line __LINE__ "java/virtual_machine.cpp"

// DLL Belonging //---------------------------------------------------------------------------------
#define JAVA_DLL

// Headers //---------------------------------------------------------------------------------------
#include <bpp/java/virtual_machine.hpp> /*INTERFACE*/

namespace bpp {

// Namespaces //------------------------------------------------------------------------------------
#define public_area  javaVirtualMachine
#define private_area javaVirtualMachine_private
#define dll_export   DLL_EXPORT

namespace public_area  {}
namespace private_area {}

static_module_name("Java/Virtual_machine");

// Initialization //--------------------------------------------------------------------------------
#undef iniJavaVirtualMachine
static_constant(private_area::clInitializer,goInitializer);

// Errors //----------------------------------------------------------------------------------------
namespace public_area {
 static_error erClassPathParameter;
 static_error erJavaMaximumHeapParameter;
 static_error erJavaMinimumHeapParameter;
 static_error erJavaThreadAttaching;
 static_error erJavaThreadDetaching;
 static_error erVirtualMachineCreation;
 static_error erVirtualMachineLocationParameter;
 static_error erVirtualMachineNotLoaded;
}

// Constants & Variables //-------------------------------------------------------------------------
static_constant(tyString,goClassPath);
static_constant(tyCardinal,goMaxHeapSize);
static_constant(tyCardinal,goMinHeapSize);
dynamic_constant(clDynamicLibrary,goLibrary);
dynamic_constant(clVirtualMachine,goVirtualMachine);
static_constant(clMutex,goMutex);

namespace private_area { function tyCreateVMFunction createVirtualMachine; }

// Static Members //--------------------------------------------------------------------------------
namespace public_area  {}
namespace private_area {}

// Functions Implementation //----------------------------------------------------------------------
namespace public_area {
 //--------------------------------------------------------------------------------GetVirtualMachine
 /*FUNCTION*/ /* Gets the Java Virtual Machine to which a context is attached. */
 function void getVirtualMachine(jyContext agContext) {
  safe_yes ( private_area::goMutex.lock(); )

  if (private_area::goVirtualMachine==nil)
   private_area::goVirtualMachine=new_object(clVirtualMachine(agContext));

  safe_yes ( private_area::goMutex.release(); )
 }
 //-------------------------------------------------------------------------------LoadVirtualMachine
 /*FUNCTION*/ /* Loads a Java Virtual Machine into memory if none is already there. */
 function void loadVirtualMachine(void) {
  safe_yes ( private_area::goMutex.lock(); )

  createThread((tyFunction)private_area::loadVirtualMachine,nil);

  while (private_area::goVirtualMachine==nil) {
   suspendThread(1);

   if (private_area::goVirtualMachine==nil)
    environment::out("Waiting Java Virtual Machine...",true,true);
  }

  private_area::goVirtualMachine->attachThread();
  safe_yes ( private_area::goMutex.release(); )
 }
 //-----------------------------------------------------------------------------UnloadVirtualMachine
 /*FUNCTION*/
 /* Unloads the Java Virtual Machine from memory, only if it was loaded using the function
    <CODE>loadVirtualMachine</CODE>. */
 function void unloadVirtualMachine(void) {
  safe_yes ( private_area::goMutex.lock(); )

  if (private_area::goVirtualMachine!=nil) {
   private_area::goVirtualMachine->detachThread();
   delete_object(private_area::goVirtualMachine);
   private_area::goVirtualMachine=nil;
  }

  safe_yes ( private_area::goMutex.release(); )
 }
 //-----------------------------------------------------------------------------------VirtualMachine
 /*FUNCTION*/
 /* Returns the Java Virtual Machine loaded into memory. Before any use of this function,
    <CODE>loadVirtualMachine</CODE> or <CODE>getVirtualMachine</CODE> has to be called. */
 function ctVirtualMachine & virtualMachine(void) {
  method_name("virtualMachine");

  safe_yes ( private_area::goMutex.lock(); )

  if (private_area::goVirtualMachine==nil) {
   safe_yes ( private_area::goMutex.release(); )
   send_error(erVirtualMachineNotLoaded);
  }

  safe_yes ( private_area::goMutex.release(); )
  return (*private_area::goVirtualMachine);
 }
}

namespace private_area {
 //-------------------------------------------------------------------------------LoadVirtualMachine
 void loadVirtualMachine(void) {
  if (private_area::goVirtualMachine==nil)
   private_area::goVirtualMachine=new_object(clVirtualMachine());

  while (private_area::goVirtualMachine!=nil) suspendThread(1);
 }
}

// V i r t u a l M a c h i n e  Implementation //---------------------------------------------------
namespace public_area {
 //--------------------------------------------------------------------------------------Constructor
 /*METHOD clVirtualMachine */ /* Builds a Java Virtual Machine from a dynamic library. */
 property clVirtualMachine::clVirtualMachine(void) : atEmbedded(false),atJniPointer(nil) {
  method_name("virtualMachine::constructor");

  if (private_area::createVirtualMachine==nil) send_error(erVirtualMachineCreation);

  #ifdef JAVA_NATIVE_INTERFACE_YES
   jyContext lcContext = nil;
   jyInteger lcResult;

   #ifdef JNI_VERSION_1_2
    JavaVMInitArgs lcArguments;
    JavaVMOption   lcOptionS[3];
    clString       lcString1;
    clString       lcString2;
    clString       lcString3;

    lcString1=clString("-Djava.class.path=")+private_area::goClassPath;
    lcString2=clString("-Xms")+standard::string(private_area::goMinHeapSize)+"m";
    lcString3=clString("-Xmx")+standard::string(private_area::goMaxHeapSize)+"m";

    lcOptionS[0].optionString=lcString1.data();
    lcOptionS[1].optionString=lcString2.data();
    lcOptionS[2].optionString=lcString3.data();

    lcArguments.version=JNI_VERSION_1_2;
    lcArguments.options=lcOptionS;
    lcArguments.nOptions=3;
    lcArguments.ignoreUnrecognized=java_false;

    lcResult=private_area::createVirtualMachine(&atJniPointer,&lcContext,&lcArguments);
   #else
    JDK1_1InitArgs lcArguments;
    tyString       lcClassPath;

    lcClassPath=new_array(char,maximumPathLength());
    lcArguments.version=JNI_VERSION_1_1;
    lcArguments.minHeapSize=private_area::goMinHeapSize*1024*1024;
    lcArguments.maxHeapSize=private_area::goMaxHeapSize*1024*1024;
    JNI_GetDefaultJavaVMInitArgs(&lcArguments);

    bpp::low::string::copy(lcClassPath,lcArguments.classpath);
    bpp::low::string::concatenate(lcClassPath,pathSeparator());
    bpp::low::string::concatenate(lcClassPath,private_area::goClassPath);

    lcArguments.classpath=lcClassPath;
    lcResult=private_area::createVirtualMachine(&atJniPointer,&lcContext,&lcArguments);
    delete_array(lcClassPath);
   #endif

   if (lcResult<0) send_error(erVirtualMachineCreation);
   atEmbedded=true;
  #else
   send_error(erVirtualMachineCreation);
  #endif
 }
 //--------------------------------------------------------------------------------------Constructor
 /*METHOD clVirtualMachine */ /* Builds the virtual machine attached to a given context. */
 property clVirtualMachine::clVirtualMachine(jyContext agContext)
 : atEmbedded(false),atJniPointer(nil) {
  method_name("virtualMachine::constructor");

  jni_yes (
   if (agContext->GetJavaVM(&atJniPointer)<0)
   send_error(erVirtualMachineCreation);
  )

  jni_no ( send_error(erVirtualMachineCreation); )
 }
 //---------------------------------------------------------------------------------------Destructor
 /*METHOD clVirtualMachine */ /* Destructs the Java Virtual Machine. */
 property clVirtualMachine::~clVirtualMachine(void) {
  jni_yes (
   if (atEmbedded and atJniPointer!=nil) {
    if (context()->ExceptionOccurred()!=nil) {
     environment::nextLine();
     environment::out("[!] Warning: A Java exception has been thrown.",true,true);
     environment::nextLine();
     context()->ExceptionDescribe();
     environment::nextLine();
    }

    //atJniPointer->DestroyJavaVM();
   }
  )
 }
 //-------------------------------------------------------------------------------------AttachThread
 /*METHOD clVirtualMachine */ /* Attaches the current thread to the virtual machine. */
 property void clVirtualMachine::attachThread(void) const {
  method_name("virtualMachine::attachThread");

  #ifdef JAVA_NATIVE_INTERFACE_YES
   jyInteger lcResult;

   #ifdef JNI_VERSION_1_2
    tyPointer lcContext = nil;
   #else
    jyContext lcContext = nil;
   #endif

   lcResult=atJniPointer->AttachCurrentThread(&lcContext,nil);
   if (lcResult<0) send_error(erJavaThreadAttaching);
  #else
   send_error(erJavaThreadAttaching);
  #endif
 }
 //------------------------------------------------------------------------------------------Context
 /*METHOD clVirtualMachine */ /* Returns the Java context of the current thread. */
 property jyContext clVirtualMachine::context(void) const {
  method_name("virtualMachine::context");

  #ifdef JAVA_NATIVE_INTERFACE_YES
   jyInteger lcResult;

   #ifdef JNI_VERSION_1_2
    tyPointer lcContext = nil;
   #else
    jyContext lcContext = nil;
   #endif

   lcResult=atJniPointer->AttachCurrentThread(&lcContext,nil);
   if (lcResult<0) send_error(erJavaThreadAttaching);
   return (reinterpret_cast<jyContext>(lcContext));
  #else
   send_error(erJavaThreadAttaching);
   return (nil);
  #endif
 }
 //-------------------------------------------------------------------------------------DetachThread
 /*METHOD clVirtualMachine */ /* Detaches the current thread from the virtual machine. */
 property void clVirtualMachine::detachThread(void) const {
  method_name("virtualMachine::detachThread");

  jni_yes ( if (atJniPointer->DetachCurrentThread()<0) send_error(erJavaThreadDetaching); )
  jni_no ( send_error(erJavaThreadDetaching); )
 }
}

// I n i t i a l i z e r  Implementation //---------------------------------------------------------
namespace private_area {
 //--------------------------------------------------------------------------------------------Start
 property void clInitializer::start(void) {
  clString lcLibraryName;

  if (atCounter++ == 0) {
   try {
    #include <bpp/modules.hpp> /*NEED*/
    registerStop(this);
    environment::informInitialization(goModuleName);

    jni_yes ( method_name("initializer::constructor"); )

    erClassPathParameter.create("The 'class_path' parameter is missing.");
    erJavaMaximumHeapParameter.create("The 'java_maximum_heap_size' parameter is missing.");
    erJavaMinimumHeapParameter.create("The 'java_minimum_heap_size' parameter is missing.");
    erJavaThreadAttaching.create("Java - Can't attach the thread to the Java Virtual Machine.");
    erJavaThreadDetaching.create("Java - Can't detach the thread from the Java Virtual Machine.");
    erVirtualMachineCreation.create("Java - Can't create the Java Virtual Machine.");
    erVirtualMachineLocationParameter.create("The 'java_virtual_machine_location' parameter is missing.");
    erVirtualMachineNotLoaded.create("Java - The Java Virtual Machine isn't loaded.");

    #ifdef JAVA_NATIVE_INTERFACE_YES
     if (environment::parameters().count(clString("class_path"))==0)
      send_error(erClassPathParameter);

     if (environment::parameters().count(clString("java_virtual_machine_location"))==0)
      send_error(erVirtualMachineLocationParameter);

     goClassPath = copy(environment::parameter(clString("class_path")));
     lcLibraryName=environment::parameter(clString("java_virtual_machine_location")).data();

     if (environment::parameters().count(clString("java_minimum_heap_size"))==0)
      send_error(erJavaMinimumHeapParameter);

     if (environment::parameters().count(clString("java_maximum_heap_size"))==0)
      send_error(erJavaMaximumHeapParameter);

     goMinHeapSize = cardinal(environment::parameter(clString("java_minimum_heap_size")).data());
     goMaxHeapSize = cardinal(environment::parameter(clString("java_maximum_heap_size")).data());

     #ifdef JAVA_VIRTUAL_MACHINE_DYNAMIC
      if (lcLibraryName=="dummy.dll") {
       goLibrary            = nil;
       createVirtualMachine = nil;
      }
      else {
       goLibrary            = new_object(clDynamicLibrary(lcLibraryName.data()));
       createVirtualMachine = (tyCreateVMFunction)goLibrary->symbol("JNI_CreateJavaVM");
      }
     #else
      goLibrary            = nil;
      createVirtualMachine = (tyCreateVMFunction)JNI_CreateJavaVM;
     #endif
    #endif

    jni_no (
     goClassPath          = copy(clString("."));
     goLibrary            = nil;
     createVirtualMachine = nil;
    )

    goVirtualMachine = nil;
   }

   initializer_catch;
  }
 }
 //---------------------------------------------------------------------------------------------Stop
 property void clInitializer::stop(void) {
  try {
   environment::informTermination(goModuleName);

   if (goVirtualMachine!=nil) delete_object(goVirtualMachine);
   delete_array(goClassPath);
   if (goLibrary!=nil) delete_object(goLibrary);
  }

  initializer_catch;
 }
}

// End //-------------------------------------------------------------------------------------------
}