/** @mainpage Qore jni Module

    @tableofcontents

    @section jniintro jni Module Introduction

    The \c jni module allows for Java APIs to be used in %Qore as if the Java APIs were %Qore APIs and vice-versa, as
    %Qore APIs can be dynamically imported into Java programs at runtime; additionally a
    @ref org.qore.jni.compiler.QoreJavaCompiler "custom Java compiler" implementation is included that allows for
    building bytecode with the @ref org.qore.jni.QoreURLClassLoader "Qore class loader" that supports dynamic byte
    code generation for %Qore and Python language bindings; see @ref jni_dynamic_import_in_java for more
    information.

    Native API imports and bindings as well as run-time data translations between Java and %Qore and Java and Python
    are supported.

    Java bytecode generation for dynamic API generation is provided by
    <a href="https://bytebuddy.net/">Byte Buddy</a>, an excellent and flexible open-source API for Java bytecode
    generation.

    Also included with the \c jni module:
    - <a href="../../MqttDataProvider/html/index.html">MqttDataProvider</a> a data provider API for MQTT messaging.

    This module is released under the MIT license (see \c COPYING.MIT in the source distribution for more
    information).  The module is tagged as such in the module's header (meaning it can be loaded unconditionally
    regardless of how the %Qore library was initialized).

    To use the module in a %Qore script, use the \c %%requires directive as follows:
    @code %requires jni @endcode

    Helper %Qore classes provided by this module:
    |!Class|!Description
    |@ref Jni::org::qore::jni::JavaArray "JavaArray"|a convenience class for using Java Arrays in %Qore
    |@ref Jni::org::qore::jni::QoreInvocationHandler "QoreInvocationHandler"|a convenience class for executing \
        %Qore-language callbacks from Java

    Helper %Qore functions provided by this module:
    |!Function|!Description
    |@ref Jni::org::qore::jni::get_version() "get_version()"|Returns the version of the JNI API
    |@ref Jni::org::qore::jni::get_byte_code() "get_byte_code()"|Returns the dynamically generated Java byte code of \
        the given %Qore class
    |@ref Jni::org::qore::jni::implement_interface() "implement_interface()"|Creates a Java object that implements \
        given interface using an invocation handler
    |@ref Jni::org::qore::jni::invoke() "invoke()"|Invokes a method with the given arguments
    |@ref Jni::org::qore::jni::invoke_nonvirtual() "invoke_nonvirtual()"|Invokes a method with the given arguments \
        in a non-virtual way; meaning that even if the object provided is a child class, the method given in the \
        first argument is executed
    |@ref Jni::org::qore::jni::load_class() "load_class()"|Loads a Java class with given name and returns a \
        \c java::lang::Class object
    |@ref Jni::org::qore::jni::new_array() "new_array()"|Creates a @ref Jni::org::qore::jni::JavaArray "JavaArray" \
        object of the given type and size
    |@ref Jni::org::qore::jni::set_save_object_callback() "set_save_object_callback()"|Sets the object lifecycle \
        management callback; see @ref jni_qore_object_lifecycle_management for more information

    @section jni_examples Examples

    @par Java Dynamic Python Import Example:
    @code{.java}
// import the "xml" module and all symbols from xml.etree.ElementTree into Java
import pythonmod.xml.etree.ElementTree.*;

// extend the pythonmod.xml.etree.ElementTree.ElementTree class
public class QoreDynamicTest10 extends ElementTree {
    // we need to declare that our constructor can throw an exception
    public QoreDynamicTest10() throws Throwable {
    }

    // parse some XML and then return the value of the given element
    public static Object test(String str, String name) throws Throwable {
        Element elem = (Element)pythonmod.xml.etree.ElementTree.$Functions.fromstring(str);
        Element e0 = elem.find(name);
        Object rv = e0.getMemberValue("text");
        return rv;
    }
}

    @endcode

    @par Java Dynamic %Qore Import Example:
    @code{.java}
import java.util.Arrays;
import java.util.Iterator;

// import ::Qore::AbstractIterator as an abstract Java base class
import qore.Qore.AbstractIterator;

// this class wraps a Java Iterator as a Qore AbstractIterator
class QoreIteratorTest extends AbstractIterator {
    private Iterator i;

    QoreIteratorTest(Iterator i) throws Throwable {
        this.i = i;
    }

    public boolean next() {
        return i.hasNext();
    }

    public Object getValue() {
        return i.next();
    }

    public boolean valid() {
        return true;
    }
}
    @endcode

    @par %Qore JMS Example:
    @code{.py}
 #!/usr/bin/env qore

 %new-style
 %require-types
 %strict-args
 %enable-all-warnings

 %requires jni
 %requires QUnit
 %requires Util

 # import Java classes to our script
 %module-cmd(jni) import javax.naming.InitialContext
 %module-cmd(jni) import javax.jms.*

 # add environment variable $GLASSFISH_JAR to the dynamic classpath (if set)
 %module-cmd(jni) add-classpath $GLASSFISH_JAR

 %exec-class Main

 public class Main inherits QUnit::Test {
     private {
         Counter c(1);
         auto data;

         const TextMsg = "Hello, world!";
     }

     constructor() : Test("JMS test", "1.0", \ARGV) {
         addTestCase("base test", \testJms());

         # Return for compatibility with test harness that checks return value.
         set_return_value(main());
     }

     testJms() {
         # these properties are the default; included here to provide an example for connecting to a remote server
         Properties props();
         props.setProperty("java.naming.factory.initial", "com.sun.enterprise.naming.SerialInitContextFactory");
         props.setProperty("org.omg.CORBA.ORBInitialHost", "localhost");
         props.setProperty("org.omg.CORBA.ORBInitialPort", "3700");

         InitialContext ctx(props);

         Connection connection =
            cast<ConnectionFactory>(ctx.lookup("jms/__defaultConnectionFactory")).createConnection();
         Session session = connection.createSession(Session::CLIENT_ACKNOWLEDGE);

         Destination queue = cast<Destination>(ctx.lookup("abc"));

         MessageProducer producer = session.createProducer(queue);
         MessageConsumer consumer = session.createConsumer(queue);

         # in order to implement the MessageListener interface for the callback, we have to use implement_interface()
         # as follows:
         ClassLoader loader = connection.getClass().getClassLoader();
         consumer.setMessageListener(cast<MessageListener>(implement_interface(loader,
            new QoreInvocationHandler(\messageCallback()), Class::forName("javax.jms.MessageListener", True,
                loader))));

         connection.start();

         TextMessage message = session.createTextMessage();
         message.setText(TextMsg);

         producer.send(message);
         # wait for message to be received
         c.waitForZero();

         assertEq(TextMsg, data);

         # unset the listener and exit
         consumer.setMessageListener(NOTHING);
     }

     # JMS message callback for the MessageListener interface
     messageCallback(Method method, *list args) {
         TextMessage msg = args[0];
         # ignore redeliveries
         if (msg.getJMSRedelivered())
             return;
         data = msg.getText();
         msg.acknowledge();
         if (m_options.verbose)
             printf("*** JMS message received: %y (id: %y)\n", msg.getText(), msg.getJMSMessageID());
         c.dec();
     }
 }
    @endcode

    @section jniinit JVM Initialization

    The JVM is initialized when the module is loaded.  It is possible to disable JIT when the module is loaded,
    however the module is loaded an initialized before module parse commands are processed, therefore to tell the
    module to disable JIT, the following environment variable must be set to \c 1:
    - <tt>QORE_JNI_DISABLE_JIT=1</tt>

    Heap sizes can be controlled by the following environment variables:
    - \c QORE_JNI_MIN_HEAP_SIZE: will set the <tt><b>-Xms</b></tt> JVM initialization option for the minimum heap size
    - \c QORE_JNI_MAX_HEAP_SIZE: will set the <tt><b>-Xmx</b></tt> JVM initialization option for the maximum heap size

    For example:
    @verbatim
# to initialize the JVM with a minimum and maximum heap size of 20MB
QORE_JNI_MIN_HEAP_SIZE=20m QORE_JNI_MAX_HEAP_SIZE=20m qore -l jni script.q
    @endverbatim

    @subsection jni_signals Signals Used By the JVM

    The JVM requires signals to run properly, and, while %Qore initializes the JVM with reduced signals (using the
    <tt>-Xrs</tt> argument to the JVM), certain signals are critical to the operation of the JVM and cannot be used by
    %Qore programs.

    The signals in the following table are reserved by the JVM.

    <b>Signals Reserved by the JVM</b>
    |!Signal|!Description
    |\c SIGTRAP|Trap exception
    |\c SIGSEGV|Segmentation violation
    |\c SIGBUS|Bus error
    |\c SIGCHLD|Child process terminated
    |\c SIGILL|Illegal instruction
    |\c SIGFPE|Floating-point exception

    If any %Qore code has a signal handler assigned to any of the above signals (for the signals that can be
    assigned), the \c jni module cannot be initialized and will fail to load.

    @section jni_use_java_in_qore Using Java APIs in Qore

    @subsection jniimport Importing Java APIs into Qore

    To import Java APIs into the %Qore program; any of the following jni-module-specific parse directives can be used,
    each of which also causes the jni module to be loaded and initialized:
    - @code{.qore} %module-cmd(jni) import java.namespace.path.* @endcode imports the given wilcard path or class into
      the %Qore program
    - @code{.qore} %module-cmd(jni) add-classpath filesystem/path:another/path @endcode adds the given paths to the
      runtime dynamic classpath; supports environment variable substitution
    - @code{.qore} %module-cmd(jni) add-relative-classpath ../relative/path @endcode adds the given paths as relative
      to the current program to the runtime dynamic classpath

    All classes in \c java.lang.* are imported implicitly.  Referencing an imported class in %Qore code
    causes a %Qore class to be generated dynamically that presents the Java class.   Instantiating a %Qore class
    based on a Java class also instantiates an internal Java object that is attached to the %Qore object.  Calling
    methods on the object will cause the same Java methods to be called with any arguments provided.

    @see @ref jni_class_mapping for more information

    @subsection jni_define_class_parse_time Defining Java Classes at Parse Time in Qore

    The jni module also supports defining classes at parse time with the following jni-module-specific parse
    directives:
    - @code{.qore} %module-cmd(jni) define-class <name> <base 64 bytecode> @endcode defines the given class with
      \c &lt;name&gt; as the Java internal name for the class (ex: \c my/package/MyClassName)
    - @code{.qore} %module-cmd(jni) define-pending-class <name> <base 64 bytecode> @endcode adds the given class and
      bytecode as a pending class to be defined when required by a \c define-class directive; this should be used for
      inner classes and has been implemented to allow circular dependencies in such classes to be resolved;
      \c &lt;name&gt; is the Java internal name for the inner class (ex: \c my/package/MyClassName$1)

    @subsection jni_set_property_parse_time Setting Java System Properties at Parse Time in Qore

    The jni module also supports setting Java system properties at parse time with the following jni-module-specific
    parse directive:
    - @code{.qore} %module-cmd(jni) set-property <propname> <value> @endcode sets the given property at parse time

    @subsection jni_class_mapping Java Class and Package Mapping in Qore

    @subsubsection jni_package_mapping Java Package to Qore Namespace Mapping

    The \c jni module maps Java packages to %Qore namespaces.  Therefore in Java \c java.lang.String can be referred
    to as \c java::lang::String in Qore as in the following example:
    @code{.py}
    java::lang::String str();
    @endcode

    The \c jni module attempts to create %Qore classes that match the source Java class as closely as possible to
    maximize the usefulness of Java APIs in %Qore.

    When creating %Qore class methods from the corresponding Java source method, types are mapped according to
    @ref jni_java_to_qore.  Because %Qore has fewer types than Java, this can lead to method signature collissions in
    the generated %Qore class.

    In such cases, only the first method returned by the internal call to \c java.lang.Class.getDeclaredMethods() is
    mapped.

    To call an unmapped method, use reflection to get a \c java.lang.Method object and make the call using
    \c java.lang.Method.invoke() as in the folllowing example:
    @code{.py}
    StringBuilder stringBuilder("");
    Method stringBuilderAppendLong = stringBuilderClass.getMethod("append", Long::TYPE);
    stringBuilderAppendLong.invoke(stringBuilder, 20);
    printf("%y\n", stringBuilder.toString());
    @endcode

    @subsubsection jniclassmapping Java Inner Classes in Qore

    Java inner classes are mapped as %Qore classes by appending \c "__" to the outer class's name and then appending
    the name of the inner class.

    For example:
    @code{.java}
class Outer {
   public Outer() {
   }

   public class Inner {
       public Inner() {
       }
   }
}
    @endcode

    The inner class would be created as \c "Outer__Inner" in %Qore.

    @subsubsection jni_class_subclassing Subclassing Java Classes in Qore

    When subclassing Java classes in %Qore, the %Qore class code is not executed by Java when dispatching method calls
    in Java itself.  This means that overriding Java methods in %Qore is only valid for code executed from %Qore.

    To override a Java method when called from Java, you must subclass the class in Java as well and import the child
    class into your %Qore program.

    One exception to this limitation is with interfaces; programmers can use the
    @ref Jni::org::qore::jni::QoreInvocationHandler "QoreInvocationHandler" class to execute %Qore-language callbacks
    based on Java interfaces; see the documentation for
    @ref Jni::org::qore::jni::QoreInvocationHandler "QoreInvocationHandler" for more information.

    @subsubsection jni_class_fields Java Class Fields to Qore Class Mappings

    Java fields are mapped to different %Qore class members according to the Java type according to the following
    table.

    <b>Java Field to Qore Class Member Mappings</b>
    |!Java|!Qore|!Qore Example
    |<tt><b>static final</b></tt>|%Qore class constants|@code{.py}MyClass::myField@endcode
    |<tt><b>static</b></tt> (non-final)|%Qore static class members|@code{.py}obj.myField@endcode
    |all others|normal %Qore class members|@code{.py}obj.myField@endcode

    For example:
    @code{.py}
System::out.println("testing...");
    @endcode

    @note Java field values are only stored in Java; they are not mirrored in %Qore.  Java field values are accessed
    with the class's \c memberGate() method; updating a Java object's field value in Qore will not cause the field
    value to be updated in Java.  To update the field's value from %Qore in Java, use
    \c java::lang::reflect::Field::set().

    @subsubsection jni_access_mapping Java Access Mapping in Qore

    Java access modifiers are mapped directly to Qore access modifiers as in the following table.

    <b>Java to Qore Access Mappings</b>
    |!Java|!Qore
    |\c\b public|\c\b public
    |\c\b protected|\c\b private (or \c\b private:hierarchy)
    |\c\b private|\c\b private:internal

    @subsection jni_classloader Java Classloader

    There is a global classloader for all Java objects reachable without special classpath.  For all other objects,
    each %Qore @ref Qore::Program "Program" container has its own custom classloader provided by the \c jni module
    that supports a dynamic classpath.

    The %Qore @ref Qore::Program "Program"-specific dynamic classpath can be set with the \c import commands as
    documented in @ref jniimport.

    Explicit class loaders can also be used as in Java as in the following example:
    @code{.py}
    softlist<URL> urls = (new URL("file:///my/dir/my-api.jar"));
    URLClassLoader classLoader(urls);
    Class myClass = classLoader.loadClass("MyClass");
    @endcode

    @note The class loader supplied by this module, @ref org.qore.jni.QoreURLClassLoader, supports dynamic bytecode
    generation from Python and %Qore imports.

    @subsection jni_exceptions Java Exceptions in Qore

    If a @ref org.qore.jni.QoreException exception is thrown, then it is mapped directly to a Qore exception.

    Other Java exceptions are mapped to the %Qore @ref Qore::ExceptionInfo "ExceptionInfo" hash as follows:
    - \c err: always \c "JNI-ERROR"
    - \c desc: the full Java class name of the exception class; ex: \c "java.lang.RuntimeException"
    - \c arg: the exception object itself as a %Qore object wrapping the Java object

    Exception locations including call stack locations reflect the actual Java source location(s), and in such cases
    the \c lang attribute will be \c "Java".

    @section jni_from_java Using the jni Module From Java

    Java code can use the jni module and Java classes based on %Qore code by calling
    @ref org.qore.jni.QoreJavaApi.initQore() to initialize the Qore library and the jni module with an existing JVM
    session.  This requires the platform-dependent \c qore library to be found in a directory set in the
    \c java.library.path or by setting the \c QORE_LIBRARY environment variable to the absolute path of the \c qore
    library.

    If neither of these are set, then calling @ref org.qore.jni.QoreJavaApi.initQore() will result in a
    \c java.lang.UnsatisfiedLinkError exception being raised.

    All Java classes for %Qore support are located in the \c qore-jni.jar file.

    Example Java source:
    @code{.java}
import org.qore.lang.qunit.*;

class MyTest {
    public static void main(String[] args) throws Throwable {
        Test test = new Test("MyTest", "1.0");

        test.addTestCase("test", () -> doTest(test));
        test.main();
    }

    private static void doTest(Test test) throws Throwable {
        test.assertTrue(true);
    }
}
    @endcode

    Example compilation command:
    @verbatim
    javac -cp qore-jni.jar MyTest.java
    @endverbatim

    Example run command:
    @verbatim
    java -Djava.library.path=/usr/local/lib -cp qore-jni.jar:. MyTest
    @endverbatim

    @subsection jni_dynamic_import_in_java Importing Qore and Python APIs Dynamically in Java

    Dynamic %Qore and Python API imports are supported with \c qore-jni.jar at runtime; \c qore-jni-compiler.jar
    contains a compiler API that can build bytecode that supports providing dynamic imports to the Java compiler as
    well.

    At runtime, only the \c qore-jni.jar file (and an accessible Qore shared library) are needed.

    @subsubsection jni_dynamic_imports Dynamic Import Statements

    The following special packages provide access to dynamically-generated classes:
    - <b><tt>python.</tt></b><i>[path...]</i>: indicates that the given path should be imported from %Python to Java
      (after being imported to %Qore if necessary).  Only use for %Python symbols not provided by a %Python module.
    - <b><tt>pythonmod.</tt></b><i>mod</i><b><tt>.</tt></b><i>[path...]</i>: indicates that the given %Python module
      should be loaded and imported into %Qore and the given symbols should be imported into Java.  %Python symbols
      provided by modules should be referenced with import statements using \c pythonmod as the symbols imported into
      Java will use such package names as well.
    - \c \b qore: indicates that the given path should be mapped to %Qore namespaces and/or classes; the Java package
      segments after <tt><b>qore.</b></tt> are then converted to the equivalent %Qore namespace path; cannot be used
      for %Qore symbols provided by modules
    - <b><tt>qoremod.</tt></b><i>mod</i><b><tt>.$</tt></b><i>[path...]</i>: indicates that the given %Qore module
      should be imported into the program container, and the given symbols should then be imported into Java.  %Qore
      symbols provided by modules must be referenced with import statements using \c qoremod as the symbols imported
      into Java will use such package names as well.

    For example:
    - <b><tt>import pythonmod.json.JSONEncoder;</tt></b> would cause the \c json Python module to be loaded and imported
      into %Qore and the \c JSONEncoder class to be imported into Java
    - <b><tt>import qore.Qore.Thread.ThreadPool;</tt></b> would cause the %Qore <tt>::Qore::Thread::ThreadPool</tt>
      class to be imported automatically into Java
    - <b><tt>import qoremod.RestClient.RestClient;</tt></b> would cause the \c RestClient %Qore module to be loaded
      and the \c RestClient class to be imported into Java

    %Qore and Python classes imported as above can then be used as Java classes for instantiation or as base classes
    for Java subclasses.

    @note
    - %Qore acts as a "language bridge" between Python and Java; Python can also inherit Java classes using a
      similar mechanism; Java classes are imported into %Qore and then the wrapper %Qore classes are imported
      dynamically into Python
    - The \c pythonmod and \c qoremod magic packages are used to ensure that modules are imported both at compile and
      runtime and that the resulting dynamically-generated Java classes have consistent binary names
    - Symbols imported from %Qore modules can be treated as non-module symbols by using the \c mark-module-injected
      module command; ex: @code{.qore} %module-cmd(jni) mark-module-injected InjectedModule @endcode would mark all
      symbols from %Qore module \c InjectedModule as non-module symbols
    - %Qore and %Python definitions imported into Java from the \c qoremod and \c pythonmod packages will be anchored
      under this package name even if definitions from other namespaces (from %Qore imports) are imported

    @see @ref jni_qore_object_lifecycle_default

    @par Importing Functions Into Java

    %Qore and Python functions can be imported using the special <tt><b>$Functions</b></tt> class name; functions in a
    %Qore namespace are imported automatically into a special class with this name as static methods in the
    corresponding dynamic Java package.

    @par Importing Constants Into Java

    %Qore constants can be imported using the special <tt><b>$Constants</b></tt> class name; constants in a %Qore
    namespace are imported automatically into a special class with this name as <tt><b>static final</b></tt> fields in
    the corresponding dynamic Java package.

    @subsubsection jni_dynamic_import_classloader Running Java Programs Using Dynamic Imports

    You must use the @ref org.qore.jni.QoreURLClassLoader class as the system classloader to run Java bytecode using
    dynamic imports.

    For example:
    @verbatim java -cp jars/my-jar.jar:jars/qore-jni.jar:. -Djava.system.class.loader=org.qore.jni.QoreURLClassLoader
        MyClass @endverbatim

    In case the Qore shared library cannot be found automatically, you may need to set the \c QORE_LIBRARY environment
    variable to the library's location as in the following example:
    @verbatim QORE_LIBRARY=/opt/qorus/lib/libqore.so java -cp jars/my-jar.jar:jars/qore-jni.jar:.
        -Djava.system.class.loader=org.qore.jni.QoreURLClassLoader MyClass @endverbatim

    @subsection jni_dynamic_imports_compile Using Dynamic API Imports While Compiling

    The @ref org.qore.jni.compiler.QoreJavaCompiler class must be used to compile bytecode using dynamic imports.

    The standard \c javac program wil not work, because it is not capable of understanding or generating the bytecode
    for the dynamic imports in the special Java package.

    Here is a Java example using the compiler:
    @code{.java}
import org.qore.jni.compiler.QoreJavaCompiler;
import org.qore.jni.compiler.CompilerOutput;

import java.nio.file.Files;

// compile and execute QoreDynamicTest.java
class MyCompiler {
    public static void main(String[] args) {
        QoreJavaCompiler compiler = new QoreJavaCompiler();
        CompilerOutput newClassData = compiler.compile("org.qore.test.QoreDynamicTest",
            Files.readString("./QoreDynamicTest.java", StandardCharsets.UTF_8));
        // get byte code with: newClassData.file.openInputStream().readAllBytes()
        Object obj = newClassData.cls.getDeclaredConstructor().newInstance();
        // ...
    }
}
    @endcode

    And here is a %Qore example:
    @code{.py}
#!/usr/bin/env qore
# -*- mode: qore; indent-tabs-mode: nil -*-

%new-style
%require-types
%strict-args
%enable-all-warnings

%requires jni

%module-cmd(jni) add-relative-classpath $MY_JARS/qore-jni.jar
%module-cmd(jni) add-relative-classpath $MY_JARS/qore-jni-compiler.jar

%module-cmd(jni) import org.qore.jni.compiler.QoreJavaCompiler
%module-cmd(jni) import org.qore.jni.compiler.CompilerOutput

# compile and execute QoreDynamicTest.java
QoreJavaCompiler compiler();
compiler::CompilerOutput newClassData = compiler.compile("org.qore.test.QoreDynamicTest",
    File::readTextFile(get_script_dir() + "/QoreDynamicTest.java"));
# get byte code with: newClassData.file.openInputStream().readAllBytes();
Object obj = newClassData.cls.getDeclaredConstructor().newInstance();
# ...
    @endcode

    @note
    - The \c qjavac helper script can be used to compile Java sources to class files; ex
      @verbatim qjavac -cp my-jar.jar MySource.java @endverbatim
    - The \c qjava2jar helper script can be used to compile Java sources using dynamic imports to a \c jar file;
      ex: @verbatim qjava2jar my-jar.jar source_path -cp some-api.jar:another-api.jar -nowarn @endverbatim

    @section jni_compat JNI Module Compatibility Options

    This module supports the following compatibility option: \c "compat-types" which, when enabled, will disable the
    following type conversions:
    - Java \c byte[] to \c binary; instead this will converted to \c list<int>
    - Java \c java.util.Map to \c hash; instead this will be converted directly

    This option can be set globally for all Program objects with
    <tt>set_module_option("jni", "compat-type", True)</tt> or
    locally for the current Program container with the \c set-compat-type module parse option like:
    <tt>%module-cmd(jni) set-compat-type true</tt> or <tt>%module-cmd(jni) set-compat-type false</tt> to
    override the global setting.

    @section jni_types Type Conversions Between Qore and Java

    The \c jni module uses reflection to automatically map Java classes to Qore classes.  This class mapping and Qore
    class creation happens at parse time (when @ref jniimport "importing Java APIs" into %Qore) and also at runtime
    (if a new class is encountered that has not already been mapped).

    There are two types of conversions from %Qore to Java:
    - @ref jni_qore2java_specific "Qore to Java Defined Type Conversions": when the type is known in advance
    - @ref jni_qore2java_default "Qore to Java Default Type Conversions": when the type is \c java.lang.Object

    @subsection jni_qore2java_specific Qore to Java Defined Type Conversions

    The following table describes type conversions for Java types when the \c jni module must convert a %Qore value to
    the declared type:

    Qore to Specific Java Type Conversions
    |!Target Java Type|!Source %Qore Type
    |\c boolean|any; conversions are made like with @ref Qore::boolean() "boolean()"
    |\c byte|any; conversions are made like with @ref Qore::int() "int()"
    |\c char|any; conversions are made like with @ref Qore::int() "int()"
    |\c short|any; conversions are made like with @ref Qore::int() "int()"
    |\c int|any; conversions are made like with @ref Qore::int() "int()"
    |\c long|any; conversions are made like with @ref Qore::int() "int()"
    |\c float|any; conversions are made like with @ref Qore::float() "float()"
    |\c double|any; conversions are made like with @ref Qore::float() "float()"

    For other types, @ref jni_qore2java_default "default conversions" are used.

    @subsection jni_qore2java_default Qore to Java Default Type Conversions

    The following table describes type conversions for Java types when the \c jni module must convert a %Qore value to
    the declared type:

    Qore to Java Default Type Conversions
    |!Source %Qore Type|!Target Java Type
    |@ref bool_type "bool"|\c boolean
    |@ref int_type "int"|\c long
    |@ref float_type "float"|\c double
    |@ref string_type "string"|\c java.lang.String
    |@ref date_type "date"|\c java.lang.Object (compile time) or \c java.time.ZonedDateTime or \
        @ref org.qore.jni.QoreRelativeTime (runtime)
    |@ref number_type "number"|\c java.math.BigDecimal
    |@ref binary_type "binary"|\c byte[]
    |@ref nothing_type "NOTHING" and @ref null_type "NULL"|\c void
    |@ref list_type "list"|java Arrays of the list type; if no list type can be found, then the Array type is \
        \c java.lang.Object
    |@ref hash_type "hash"|@ref org.qore.jni.Hash (extends \c java.util.LinkedHashMap), however any type \
        implementing \c java.util.Map that has a constructor that takes no arguments and a compatibule \c put() \
        method will be constructed / converted automatically to the desired type
    |@ref code_type "code" (closure or call reference)|@ref org.qore.jni.QoreClosure
    |@ref Jni::org::qore::jni::JavaArray "JavaArray"|\c java.lang.Array
    |@ref object_type "object"|to the Java class if the Qore object is an instantiation of \c java.lang.Object, \
        otherwise @ref org.qore.jni.QoreObject
    |all \c jni objects|direct conversion

    @note
    - %Qore 64-bit integers are automatically converted to 64-bit Java long values; to get a Java \c int value; use:
      @code{.py} new Integer(val) @endcode
    - see @ref jni_arrays for more information about conversions between %Qore lists and Java arrays

    @subsection jni_java_to_qore Java to Qore Type Conversions

    Java to %Qore Type Conversions
    |!Source Java Type|!Target %Qore Type
    |\c boolean|@ref bool_type "bool"
    |\c byte|@ref int_type "int"
    |\c byte[]|@ref binary_type "binary" (see also @ref jni_compat)
    |\c char|@ref int_type "int"
    |\c short|@ref int_type "int"
    |\c int|@ref int_type "int"
    |\c long|@ref int_type "int"
    |\c float|@ref float_type "float"
    |\c double|@ref float_type "double"
    |\c java.lang.String|@ref string_type "string"
    |\c java.sql.Date|@ref date_type "date" (@ref absolute_dates "absolute date")
    |\c java.sql.Time|@ref date_type "date" (@ref absolute_dates "absolute date" based on \c 1970-01-01)
    |\c java.sql.Timestamp|@ref date_type "date" (@ref absolute_dates "absolute date")
    |\c java.time.ZonedDateTime|@ref date_type "date" (@ref absolute_dates "absolute date")
    |<tt>@ref org.qore.jni.QoreRelativeTime</tt>|@ref date_type "date" (@ref relative_dates "relative date")
    |\c java.math.BigDecimal|@ref number_type "number"
    |\c java.lang.AbstractArray and arrays|@ref list_type "list"
    |\c java.util.Map|@ref hash_type "hash" (see also @ref jni_compat)
    |<tt>@ref org.qore.jni.QoreClosure</tt>|@ref code_type "code"
    |<tt>@ref org.qore.jni.QoreClosureMarker</tt>|@ref code_type "code"
    |all other objects|direct conversion

    @note see @ref jni_arrays for more information about conversions between %Qore lists and Java arrays

    @subsection jni_arrays Java Arrays

    Arrays are mapped directly to and from Qore lists.  When converting from a Qore list to a Java array, the list is
    scanned, and if it can be mapped to a single Java type, then an array of that type is created.  Otherwise, an
    array of \c java.lang.Object is created and the %Qore values are mapped directly to Java values.

    %Qore lists are passed by value and Java arrays are objects (which, like %Qore objects, are passed with a copy of
    a reference to the object).  To get similar functionality in %Qore, you can use the
    @ref Jni::org::qore::jni::JavaArray "JavaArray" class supplied with the \c jni module, which is automatically
    converted to a Java array object like a list, but is also passed with a copy of a reference to the object in
    %Qore.

    @subsubsection jni_array_varargs Java Arrays as Variable Arguments

    When a Java method declares variable arguments, such arguments must be generally given as a single array value (so
    a %Qore list or @ref Jni::org::qore::jni::JavaArray "JavaArray" object).  Consider the following example:
    @code{.py}
    StringBuilder stringBuilder("");
    lang::Class stringBuilderClass = lang::Class::forName("java.lang.StringBuilder");
    Method stringBuilderAppendLong = stringBuilderClass.getMethod("append", (Long::TYPE,));
    stringBuilderAppendLong.invoke(stringBuilder, (20,));
    printf("%y\n", stringBuilder.toString());
    @endcode

    In the above example, the \c java.lang.Class.getMethod() and the \c java.lang.Method.invoke() methods both take a
    variable number of arguments after the first argument.  In the mapped Qore class, these must be given as a %Qore
    list (as in the above example) or a @ref Jni::org::qore::jni::JavaArray "JavaArray" object.

    If the last argument of a Java method has an array type, then the arguments in Qore can also be given in the flat
    argument list, and the final array argument will be created automatically for the call.

    Because of this, the example above also works as follows:
    @code{.py}
    StringBuilder stringBuilder("");
    lang::Class stringBuilderClass = lang::Class::forName("java.lang.StringBuilder");
    Method stringBuilderAppendLong = stringBuilderClass.getMethod("append", Long::TYPE);
    stringBuilderAppendLong.invoke(stringBuilder, 20);
    printf("%y\n", stringBuilder.toString());
    @endcode

    @section jni_qore_object_lifecycle_management Managing the Lifecycle of Qore objects from Java

    %Qore's deterministic garbage collection approach and reliance on destructors means that %Qore objects created by
    Java must have their lifecycles managed externally.

    Java objects wrapping %Qore objects (such as @ref org.qore.jni.QoreObject and @ref org.qore.jni.QoreObjectWrapper)
    hold only weak references to %Qore objects.

    Java methods in the jni module that explicitly save strong references to %Qore objects are as follows:
    - @ref org.qore.jni.QoreObject.callMethodSave()
    - @ref org.qore.jni.QoreObject.callMethodArgsSave()
    - @ref org.qore.jni.QoreJavaApi.callFunctionSave()
    - @ref org.qore.jni.QoreJavaApi.callStaticMethodSave()
    - @ref org.qore.jni.QoreJavaApi.newObjectSave()

    The strong reference to any %Qore object returned by the preceding methods is managed in one of two ways
    described in the following sections.

    @subsection jni_qore_object_lifecycle_default Default Qore Object Lifecycle Management

    By default, %Qore objects are saved in thread-local data, so the lifecycle of the object is automatically limited
    to the existence of the thread.

    The thread-local hash key name used to save the list of objects created is determined by the value of the
    \c "_jni_save" thread-local key, if set.  If no such key is set, then \c "_jni_save" is used instead as the
    literal key for saving the list of objects.

    @note To register a Java thread in %Qore and therefore to ensure that %Qore object data is persistent in a native
    Java thread, call @ref org.qore.jni.QoreJavaApi.registerJavaThread() and then
    @ref org.qore.jni.QoreJavaApi.deregisterJavaThread() before the Java thread terminates, when you are done working
    with %Qore data, otherwise %Qore objects will be immediately deleted when the implicit %Qore thread registration
    expires after each %Qore API call.

    @subsection jni_qore_object_lifecycle_explicit Explicit Qore Object Lifecycle Management

    The lifecycle of %Qore objects can be managed explicitly by using the
    @ref Jni::org::qore::jni::set_save_object_callback() "set_save_object_callback()" function to set a callback that
    is called every time a %Qore object is created using the Java API.

    This callback must take a single object argument as in the following example:

    @code{.py}
hash<string, object> object_cache;
code callback = sub (object obj) {
    # save object in object cache, so it doesn't go out of scope
    object_cache{obj.uniqueHash()} = obj;
}
set_save_object_callback(callback);
    @endcode

    @section jni_thread_stacks Java Thread Stack size

    The Java thread stack size is set when the JVM is initialized in the \c jni module's initialization code based on
    the %Qore thread stack size.

    Note that the JVM establishes its own stack guard implementation and enforces stack sizes, even for %Qore code and
    native threads, as a result, the %Qore thread stack size limitation is also applied to the primary process thread,
    which otherwise (at least on Linux, for example), normally has a larger limit (normally 8MB on Linux).

    Therefore if the %Qore thread stack size is set to 512KB (for example), the Java thread stack size limit as well
    as the primary thread's stack size limit is set to 512KB when the \c jni module is initialized.

    The %Qore library's default thread stack size is 8MB.  This is much larger than the standard 1MB stack size for
    the JVM, however, since thread stacks are generally allocated from overcommitted memory, meaning that they start
    small and only grow on demand, a larger thread stack size does not imply a larger memory footprint for programs
    that do not require large stacks.

    @section jdbc_driver jdbc DBI Driver for Qore

    This module implements the \c "jdbc" driver for %Qore to allow %Qore (as well as other languages such as %Python
    that use %Qore as a "language bridge") to access data in databases with jdbc drivers.

    To use the \c jdbc DBI driver, use the driver name \c "jdbc".

    %Qore datasource strings do not follow the same format as standard Java JDBC URL strings, so some care must be
    taken when creating the %Qore datasource strings for the jdbc driver.

    The format for %Qore datasource strings with the jdbc driver is as follows:
    <tt><b>jdbc:</b>[</tt><em>user</em><tt>]/[</tt><em>pass</em><tt>]\@</tt><em>db</em><tt>[{</tt><em>option</em><tt>=</tt><em>val</em><tt>[,...]}]</tt> \n
    where all elements except <tt>\@</tt><em>db</em> are optional.

    The \a host, \a port, and \a charset values in %Qore datasource strings are ignored; the host and port must be
    given in the \a db value, and, if the jdbc driver's URL format requires the use of \c @ or \c : characters, then
    a dummy value must be provided for the \a db value, and the @ref jdbc_option_url "url option" must be used
    instead.

    Some example %Qore datasource strings for the \c jdbc driver:
    - <tt>jdbc:user/pass@postgresql:dbname{classpath=/usr/share/java/postgresql.jar}</tt>
    - <tt>jdbc:user/pass@firebird:?serverName=hq&databaseName=/var/lib/firebird/data/mydb.fdb&encoding=WIN1252{classpath=/usr/share/java/jna-5.12.1.jar:/usr/share/java/jaybird-5.0.0.java11.jar}</tt>
    - <tt>jdbc:user/pass@ignored{url=oracle:thin:@hostname:1521:dbserver,classpath=/usr/lib/oracle/21/client64/lib/ojdbc8.jar}</tt>

    @note the initial \c jdbc: in the \c db value is optional; if missing, it is added automatically by the %Qore
    \c jdbc driver when the internal URL string is passed to the internal Java JDBC call.

    @subsection jdbc_driver_options jdbc DBI Driver Options

    The jdbc driver supports the following DBI options:
    - \c "classpath": @ref jdbc_option_classpath "sets the classpath" with jar or class files providing the \c jdbc
      driver
    - \c "numeric-numbers": return received \c SQL_NUMERIC and \c SQL_DECIMAL values as arbitrary-precision numbers
      (Qore number values)
    - \c "optimal-numbers": return received \c SQL_NUMERIC and \c SQL_DECIMAL values as integers if possible, if not
      return them as an arbitrary-precision numbers; this is the default
    - \c "string-numbers": return received \c SQL_NUMERIC and \c SQL_DECIMAL values as strings (for backwards-
      compatibility)
    - \c "url": @ref jdbc_option_url "sets the URL" for the \c jdbc driver in case the database value in the %Qore
      datasource connection string cannot accommodate the \c jdbc URL because of special characters

    @note By default \c optimal-numbers is set on the driver for maximum compatibility with other %Qore DBI drivers

    @subsubsection jdbc_option_classpath jdbc classpath Option

    The \c classpath option allows for the classpath to be set before the JVM loads the Java \c jdbc driver.  The
    value of this option must be a string and is subject to environment variable substitution before being used.

    If this option is used, then the \c java.util.ServiceLoader for \c "java.sql.Driver" objects is run and iterated
    after updating the classpath, to ensure that any new drivers are loaded and available to drivers loaded after the
    initial driver is loaded, otherwise the \c java.sql.DriverManager will not see the new drivers as they were not
    present in the classpath during on-demand static initialization).

    @subsubsection jdbc_option_url jdbc url Option

    The \c url option allows for \c jdbc URLs to be used that contain special characters like \c @ and \c : that would
    cause the datasource to fail to parse when used as a database name.

    @par url Option Example
    @verbatim
    Datasource ds("jdbc:user/pass@ignored{url=oracle:thin:@hostname:1521:dbserver,classpath=/usr/lib/oracle/21/client64/lib/ojdbc8.jar}");
    @endverbatim

    @note the initial \c jdbc: in the url option is optional; if missing, then it is added automatically by the %Qore
    jdbc driver when the internal URL string is passed to the internal Java JDBC call.

    @section jnireleasenotes jni Module Release Notes

    @subsection jni_2_3_1 jni Module Version 2.3.1
    - fixed a bugs handling the signal mask that could cause hard-to-debug problems such as deadlocks in JIT in the
      JVM on macOS aarch64
      (<a href="https://github.com/qorelanguage/qore/issues/4793">issue 4793</a>)
    - fixed signatures for Java methods with varargs params
      (<a href="https://github.com/qorelanguage/qore/issues/4787">issue 4787</a>)
    - fixed a bug where %Qore's thread stack size settings were not applied to Java threads
      (<a href="https://github.com/qorelanguage/qore/issues/4769">issue 4769</a>)

    @subsection jni_2_3_0 jni Module Version 2.3.0
    - added the <a href="../../MqttDataProvider/html/index.html">MqttDataProvider</a> module to allow for publishing
      and subscribing to MQTT messages
      (<a href="https://github.com/qorelanguage/qore/issues/4756">issue 4756</a>)

    @subsection jni_2_2_1 jni Module Version 2.2.1
    - fixed a bug handling class loaders in JDBC connection acquisition when called from %Qore code imported in native
      Java code; in this case dynamic dispatch is used in the internal call to \c DriverManager.getConnection() to
      ensure that the call succeeds
      (<a href="https://github.com/qorelanguage/qore/issues/4740">issue 4740</a>)

    @subsection jni_2_2_0 jni Module Version 2.2.0
    - implemented support for specifying the minimum an maximum heap sizes for the JVM on initialization from
      environment variables
      (<a href="https://github.com/qorelanguage/qore/issues/4734">issue 4734</a>)
    - fixed bugs handling dynamic Java class generation from %Qore classes with abstract methods
      (<a href="https://github.com/qorelanguage/qore/issues/4737">issue 4737</a>)

    @subsection jni_2_1_1 jni Module Version 2.1.1
    - fixed reading date/time values from MS SQL Server databases
      (<a href="https://github.com/qorelanguage/qore/issues/4705">issue 4705</a>)
    - fixed exception handling in destroying JNI program data
      (<a href="https://github.com/qorelanguage/qore/issues/4704">issue 4704</a>)

    @subsection jni_2_1_0 jni Module Version 2.1.0
    - added support for the @ref jdbc_driver "jdbc DBI driver"
      (<a href="https://github.com/qorelanguage/qore/issues/4684">issue 4684</a>)
    - fixed a bug where \c qjavac did not detect the binary path of compiled objects correctly in all cases
      (<a href="https://github.com/qorelanguage/qore/issues/4683">issue 4683</a>)
    - fixed a bug in \c qjavac where error information was not output when compiler diagnostic information was missing
      (<a href="https://github.com/qorelanguage/qore/issues/4682">issue 4682</a>)

    @subsection jni_2_0_11 jni Module Version 2.0.11
    - updated to work with the latest \c qpp from %Qore 1.12.4+

    @subsection jni_2_0_10 jni Module Version 2.0.10
    - fixed a bug where the dynamic compiler (@ref org.qore.jni.compiler.QoreJavaCompiler "QoreJavaCompiler") could
      not load classes in internal Java modules
      (<a href="https://github.com/qorelanguage/qore/issues/4619">issue 4619</a>)

    @subsection jni_2_0_9 jni Module Version 2.0.9
    - added the following new class to the Java API:
      - @ref org.qore.jni.QoreClosureMarkerImpl
    - fixed a bug where a crash could result when calling a Java varargs method from another language with no
      arguments passed
      (<a href="https://github.com/qorelanguage/qore/issues/4594">issue 4594</a>)
    - fixed a bug importing %Qore APIs using the \c code type into Java as @ref org.qore.jni.QoreClosureMarkerImpl types
      (<a href="https://github.com/qorelanguage/qore/issues/4593">issue 4593</a>)
    - fixed a bug handling %Qore constructor calls from dynamically generated Java classes
      (<a href="https://github.com/qorelanguage/qore/issues/4581">issue 4581</a>)

    @subsection jni_2_0_8 jni Module Version 2.0.8
    - fixed a bug importing %Qore abstract methods with default or optional arguments
      (<a href="https://github.com/qorelanguage/qore/issues/4570">issue 4570</a>)

    @subsection jni_2_0_7 jni Module Version 2.0.7
    - fixed a bug where missing c++ exception handling could cause a crash
      (<a href="https://github.com/qorelanguage/qore/issues/4500">issue 4500</a>)

    @subsection jni_2_0_6 jni Module Version 2.0.6
    - fixed a bug where \c qjavac wrote output files in the current working directory instead of in the directory of
      the input file
      (<a href="https://github.com/qorelanguage/qore/issues/4376">issue 4376</a>)
    - fixed a bug where %Qore and %Python functions could not be imported into the special \c $Functions Java class if
      the enclosing namespace had less than two classes in it
      (<a href="https://github.com/qorelanguage/qore/issues/4375">issue 4375</a>)
    - fixed a bug where @ref org.qore.jni.QoreURLClassLoader could not be used as the system class loader
      (<a href="https://github.com/qorelanguage/qore/issues/4374">issue 4374</a>)
    - fixed a bug where %Qore and %Python dynamic module imports were not created with consistent Java binary names;
      also to facilitate handling injected symbols, the \c mark-module-injected module command was implemented
      (<a href="https://github.com/qorelanguage/qore/issues/4373">issue 4373</a>)
    - fixed a bug where Java method args in array format were not always converted from %Qore values correctly
      (<a href="https://github.com/qorelanguage/qore/issues/4372">issue 4372</a>)
    - fixed a bug where the %Qore \c Program and Java \c ClassLoader context was not always set correctly when
      instantiating %Qore classes from Java when there were dependencies on dynamic classes, leading to class
      resolution errors
      (<a href="https://github.com/qorelanguage/qore/issues/4371">issue 4371</a>)

    @subsection jni_2_0_5 jni Module Version 2.0.5
    - fixed a memory error in exception handling in %Qore to Java tyype conversions that could cause a core dump
      (<a href="https://github.com/qorelanguage/qore/issues/4349">issue 4349</a>)

    @subsection jni_2_0_4 jni Module Version 2.0.4
    - fixed a bug handling %Qore callbacks in native Java threads
      (<a href="https://github.com/qorelanguage/qore/issues/4340">issue 4340</a>)
    - fixed a bug handling Java interfaces as parents of a Java class
      (<a href="https://github.com/qorelanguage/qore/issues/4337">issue 4337</a>)

    @subsection jni_2_0_3 jni Module Version 2.0.3
    - implemented the magic \c '$' target when importing %Qore or %Python modules with import statements with the
      \c qoremod or \c pythonmod special packages
      (<a href="https://github.com/qorelanguage/qore/issues/4304">issue 4304</a>)

    @subsection jni_2_0_2 jni Module Version 2.0.2
    - fixed a bug where vararg arguments were not handled correctly when dynamically-generated Java called a %Qore or
      %Python static method
      (<a href="https://github.com/qorelanguage/qore/issues/4302">issue 4302</a>)
    - fixed a bug where a runtime error was generated when used with Java 17 or above
      (<a href="https://github.com/qorelanguage/qore/issues/4300">issue 4300</a>)

    @subsection jni_2_0_1 jni Module Version 2.0.1
    - fixed a bug where parameters of %Qore type \c null or \c nothing would cause runtime errors when importing
      the method into Java
      (<a href="https://github.com/qorelanguage/qore/issues/4287">issue 4287</a>)
    - fixed static method calls of imported %Qore methods in Java; the calling context is now set correctly
      (<a href="https://github.com/qorelanguage/qore/issues/4286">issue 4286</a>)
    - fixed importing abstract %Qore methods into Java; Java methods are no longer marked as taking trailing vararg
      arguments
      (<a href="https://github.com/qorelanguage/qore/issues/4282">issue 4282</a>)
    - fixed importing %Qore classes with multiple inheritance into Java; methods from other non-primary base classes
      are now also imported into the dynamic Java class
      (<a href="https://github.com/qorelanguage/qore/issues/4091">issue 4091</a>)

    @subsection jni_2_0 jni Module Version 2.0
    - %Qore @ref int_type "int" values are now converted to Java \c long in order to maintain precision
    - the old hand-developed Java wrapper classes are deprecated as of this release in favor of
      @ref jni_dynamic_import_in_java "dynamic imports"
    - implemented support for dynamically importing %Qore code into Java
      (<a href="https://github.com/qorelanguage/qore/issues/4091">issue 4091</a>)

    @subsection jni_1_2 jni Module Version 1.2
    - fixed crashes handling deleted Qore program contexts
      (<a href="https://github.com/qorelanguage/qore/issues/4263">issue 4262</a>)
    - fixed crashes handling Java lambdas wrapping Qore closures and call references
      (<a href="https://github.com/qorelanguage/qore/issues/4261">issue 4261</a>)
    - fixed importing AWT classes in headless mode
      (<a href="https://github.com/qorelanguage/qore/issues/4231">issue 4231</a>)
    - fixed method calls for methods that return base types where a caller context is required (for Reflection access
      control, for example)
      (<a href="https://github.com/qorelanguage/qore/issues/4093">issue 4093</a>)
    - fixed a memory leak (or an assertion crash in debug mode) for a duplicate member error when importing Java
      classes in %Qore
      (<a href="https://github.com/qorelanguage/qore/issues/4029">issue 4029</a>)
    - added support for converting %Qore closures / call references to a Java object
      (<a href="https://github.com/qorelanguage/qore/issues/3995">issue 3995</a>)

    @subsection jni_1_1_3 jni Module Version 1.1.3
    - fixed a bug create %Qore classes wrapping Java classes that can cause a crash with debug builds of %Qore
      (<a href="https://github.com/qorelanguage/qore/issues/3976">issue 3976</a>)
    - added a runtime check to verify that the Java version is the expected version, otherwise a crash or other
      errors could result
      (<a href="https://github.com/qorelanguage/qore/issues/3969">issue 3969</a>)
    - added \c getAs*() methods to the @ref org.qore.jni.Hash class to help in retrieving key values with specific
      base types
      (<a href="https://github.com/qorelanguage/qore/issues/3957">issue 3957</a>)
    - added the @ref org.qore.lang.dataprovider.AbstractDataProcessor class
      (<a href="https://github.com/qorelanguage/qore/issues/3876">issue 3876</a>)

    @subsection jni_1_1_2 jni Module Version 1.1.2
    - use <tt>java.util.List&lt;?&gt;</tt> instead of <tt>java.util.ArrayList&lt;?&gt;</tt> as the base class for list
      conversions
      (<a href="https://github.com/qorelanguage/qore/issues/3873">issue 3873</a>)
    - added the @ref org.qore.jni.Hash class to use when passing %Qore hashes to Java to make it easier to work with
      %Qore hash data
      (<a href="https://github.com/qorelanguage/qore/issues/3868">issue 3868</a>)
    - fixed a crash in %Qore Program setup when initiated from a Qore thread with no Java context thread
      (<a href="https://github.com/qorelanguage/qore/issues/3862">issue 3862</a>)
    - fixed a crash in method invocation due to incorrect detection of the Java thread context
      (<a href="https://github.com/qorelanguage/qore/issues/3794">issue 3794</a>)
    - fixed a bug in @ref org.qore.lang.smtpclient.SmtpClient.sendMessage() "SmtpClient.sendMessage()"

    @subsection jni_1_1_1 jni Module Version 1.1.1
    - added the following Java classes:
      - @ref org.qore.lang.mailmessage.Attachment "Attachment"
      - @ref org.qore.lang.mailmessage.Message "Message"
      - @ref org.qore.lang.mailmessage.Part "Part"
      - @ref org.qore.lang.restclient.RestClient "RestClient"
      - @ref org.qore.lang.smtpclient.SmtpClient "SmtpClient"
      .
      (<a href="https://github.com/qorelanguage/qore/issues/3794">issue 3794</a>)

    @subsection jni_1_1 jni Module Version 1.1
    - added support for disabling JIT with the <tt>QORE_JNI_DISABLE_JIT=1</tt> environment variable
      (<a href="https://github.com/qorelanguage/qore/issues/3199">issue 3199</a>)
    - updated to require Java/JDK 11+
      (<a href="https://github.com/qorelanguage/qore/issues/3188">issue 3188</a>)
    - added @ref Jni::org::qore::jni::define_class() "define_class()"
      (<a href="https://github.com/qorelanguage/qore/issues/3023">issue 3023</a>)
    - added the following new class methods to the Qore API:
      - @ref Jni::org::qore::jni::JavaArray::toString() "JavaArray::toString()"
      - @ref Jni::org::qore::jni::JavaArray::deepToString() "JavaArray::deepToString()"
    - added the following new class methods to the Java API:
      - @ref org.qore.jni.QoreJavaApi.callFunctionSave() "QoreJavaApi.callFunctionSave()"
      - @ref org.qore.jni.QoreJavaApi.callFunctionSaveArgs() "QoreJavaApi.callFunctionSaveArgs()"
      - @ref org.qore.jni.QoreJavaApi.callStaticMethod() "QoreJavaApi.callStaticMethod()"
      - @ref org.qore.jni.QoreJavaApi.callStaticMethodArgs() "QoreJavaApi.callStaticMethodArgs()"
      - @ref org.qore.jni.QoreJavaApi.callStaticMethodSave() "QoreJavaApi.callStaticMethodSave()"
      - @ref org.qore.jni.QoreJavaApi.callStaticMethodSaveArgs() "QoreJavaApi.callStaticMethodSave()"
      - @ref org.qore.jni.QoreJavaApi.newObjectSave() "QoreJavaApi.newObjectSave()"
      - @ref org.qore.jni.QoreJavaApi.newObjectSaveArgs() "QoreJavaApi.newObjectSaveArgs()"
    - added the following new classes to the Java API:
      - @ref org.qore.jni.QoreClosureMarker
      - @ref org.qore.jni.QoreObject
      - @ref org.qore.jni.QoreObjectWrapper
      - @ref org.qore.jni.QoreRelativeTime
      - @ref org.qore.lang.AbstractDatasource
      - @ref org.qore.lang.AbstractIterator
      - @ref org.qore.lang.AbstractSQLStatement
      - @ref org.qore.lang.DatasourcePool
      - @ref org.qore.lang.HTTPClient
      - @ref org.qore.lang.LogCallback
      - @ref org.qore.lang.bulksqlutil.AbstractBulkOperation
      - @ref org.qore.lang.bulksqlutil.BulkInsertOperation
      - @ref org.qore.lang.bulksqlutil.BulkRowCallback
      - @ref org.qore.lang.bulksqlutil.BulkUpsertOperation
      - @ref org.qore.lang.dataprovider.AbstractDataField
      - @ref org.qore.lang.dataprovider.AbstractDataProvider
      - @ref org.qore.lang.dataprovider.AbstractDataProviderBulkOperation
      - @ref org.qore.lang.dataprovider.AbstractDataProviderBulkRecordInterface
      - @ref org.qore.lang.dataprovider.AbstractDataProviderRecordIterator
      - @ref org.qore.lang.dataprovider.AbstractDataProviderType
      - @ref org.qore.lang.mapper.Mapper
      - @ref org.qore.lang.mapper.MapperOutputRecordIterator
      - @ref org.qore.lang.reflection.Type
      - @ref org.qore.lang.soapclient.SoapClient
      - @ref org.qore.lang.sqlutil.Table
      - @ref org.qore.lang.sqlutil.AbstractTable
      - @ref org.qore.lang.sqlutil.AbstractSqlUtilBase
      - @ref org.qore.lang.tablemapper.RawSqlStatementOutboundMapper
      - @ref org.qore.lang.tablemapper.InboundIdentityTableMapper
      - @ref org.qore.lang.tablemapper.AbstractSqlStatementOutboundMapper
      - @ref org.qore.lang.tablemapper.InboundTableMapper
      - @ref org.qore.lang.tablemapper.SqlStatementOutboundMapper
    - added support for date <-> java.time.ZonedDateTime conversions
      (<a href="https://github.com/qorelanguage/qore/issues/3026">issue 3026</a>)
    - fixed the %Qore exception location when a Java exception is propagated to %Qore
      (<a href="https://github.com/qorelanguage/qore/issues/3077">issue 3077</a>)
    - fixed call stacks for exceptions to include Java call stack information in all cases
      (<a href="https://github.com/qorelanguage/qore/issues/3169">issue 3169</a>)
    - implemented the \c set-property module command to allow for Java system properties to be set while initializing
      the module
      (<a href="https://github.com/qorelanguage/qore/issues/3495">issue 3495</a>)

    @subsection jni_1_0_1 jni Module Version 1.0.1
    - fixed setting the class loader context when calling %Qore code from Java threads
      (<a href="https://github.com/qorelanguage/qore/issues/3585">issue 3585</a>)

    @subsection jni_1_0 jni Module Version 1.0
    - initial public release
*/
