Qore jni Module 2.4.0
|
The 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 custom Java compiler implementation is included that allows for building bytecode with the Qore class loader that supports dynamic byte code generation for Qore and Python language bindings; see Importing Qore and Python APIs Dynamically 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 Byte Buddy, an excellent and flexible open-source API for Java bytecode generation.
Also included with the jni
module:
This module is released under the MIT license (see 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 %requires
directive as follows:
Helper Qore classes provided by this module:
Class | Description |
JavaArray | a convenience class for using Java Arrays in Qore |
QoreInvocationHandler | a convenience class for executing Qore-language callbacks from Java |
Helper Qore functions provided by this module:
Function | Description |
get_version() | Returns the version of the JNI API |
get_byte_code() | Returns the dynamically generated Java byte code of the given Qore class |
implement_interface() | Creates a Java object that implements given interface using an invocation handler |
invoke() | Invokes a method with the given arguments |
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 |
load_class() | Loads a Java class with given name and returns a java::lang::Class object |
new_array() | Creates a JavaArray object of the given type and size |
set_save_object_callback() | Sets the object lifecycle management callback; see Managing the Lifecycle of Qore objects from Java for more information |
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 1
:
QORE_JNI_DISABLE_JIT=1
Heap sizes can be controlled by the following environment variables:
QORE_JNI_MIN_HEAP_SIZE:
will set the -Xms
JVM initialization option for the minimum heap sizeQORE_JNI_MAX_HEAP_SIZE:
will set the -Xmx
JVM initialization option for the maximum heap sizeFor example:
# 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
The JVM requires signals to run properly, and, while Qore initializes the JVM with reduced signals (using the -Xrs
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.
Signals Reserved by the JVM
Signal | Description |
SIGTRAP | Trap exception |
SIGSEGV | Segmentation violation |
SIGBUS | Bus error |
SIGCHLD | Child process terminated |
SIGILL | Illegal instruction |
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 jni
module cannot be initialized and will fail to load.
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:
All classes in 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.
The jni module also supports defining classes at parse time with the following jni-module-specific parse directives:
<name>
as the Java internal name for the class (ex: my/package/MyClassName
)define-class
directive; this should be used for inner classes and has been implemented to allow circular dependencies in such classes to be resolved; <name>
is the Java internal name for the inner class (ex: my/package/MyClassName$1
)The jni module also supports setting Java system properties at parse time with the following jni-module-specific parse directive:
The jni
module maps Java packages to Qore namespaces. Therefore in Java java.lang.String
can be referred to as java::lang::String
in Qore as in the following example:
The 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 Java to Qore Type Conversions. 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 java.lang.Class.getDeclaredMethods()
is mapped.
To call an unmapped method, use reflection to get a java.lang.Method
object and make the call using java.lang.Method.invoke()
as in the folllowing example:
Java inner classes are mapped as Qore classes by appending "__"
to the outer class's name and then appending the name of the inner class.
For example:
The inner class would be created as "Outer__Inner"
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 QoreInvocationHandler class to execute Qore-language callbacks based on Java interfaces; see the documentation for QoreInvocationHandler for more information.
Java fields are mapped to different Qore class members according to the Java type according to the following table.
Java Field to Qore Class Member Mappings
Java | Qore | Qore Example |
static final | Qore class constants | MyClass::myField
|
static (non-final) | Qore static class members | obj.myField
|
all others | normal Qore class members | obj.myField
|
For example:
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 java::lang::reflect::Field::set()
.Java access modifiers are mapped directly to Qore access modifiers as in the following table.
Java to Qore Access Mappings
Java | Qore |
public | public |
protected | private (or private:hierarchy) |
private | private:internal |
There is a global classloader for all Java objects reachable without special classpath. For all other objects, each Qore Program container has its own custom classloader provided by the jni
module that supports a dynamic classpath.
The Qore Program-specific dynamic classpath can be set with the import
commands as documented in Importing Java APIs into Qore.
Explicit class loaders can also be used as in Java as in the following example:
If a org.qore.jni.QoreException exception is thrown, then it is mapped directly to a Qore exception.
Other Java exceptions are mapped to the Qore ExceptionInfo hash as follows:
err:
always "JNI-ERROR"
desc:
the full Java class name of the exception class; ex: "java.lang.RuntimeException"
arg:
the exception object itself as a Qore object wrapping the Java objectException locations including call stack locations reflect the actual Java source location(s), and in such cases the lang
attribute will be "Java"
.
Java code can use the jni module and Java classes based on Qore code by calling org.qore.jni.QoreJavaApi.initQore() to initialize the Qore library and the jni module with an existing JVM session. This requires the platform-dependent qore
library to be found in a directory set in the java.library.path
or by setting the QORE_LIBRARY
environment variable to the absolute path of the qore
library.
If neither of these are set, then calling org.qore.jni.QoreJavaApi.initQore() will result in a java.lang.UnsatisfiedLinkError
exception being raised.
All Java classes for Qore support are located in the qore-jni.jar
file.
Example Java source:
Example compilation command:
javac -cp qore-jni.jar MyTest.java
Example run command:
java -Djava.library.path=/usr/local/lib -cp qore-jni.jar:. MyTest
Dynamic Qore and Python API imports are supported with qore-jni.jar
at runtime; 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 qore-jni.jar
file (and an accessible Qore shared library) are needed.
The following special packages provide access to dynamically-generated classes:
python.
[path...]: 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.pythonmod.
mod.
[path...]: 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 pythonmod
as the symbols imported into Java will use such package names as well.qore: indicates
that the given path should be mapped to Qore namespaces and/or classes; the Java package segments after qore.
are then converted to the equivalent Qore namespace path; cannot be used for Qore symbols provided by modulesqoremod.
mod.$
[path...]: 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 qoremod
as the symbols imported into Java will use such package names as well.For example:
import pythonmod.json.JSONEncoder;
would cause the json
Python module to be loaded and imported into Qore and the JSONEncoder
class to be imported into Javaimport qore.Qore.Thread.ThreadPool;
would cause the Qore ::Qore::Thread::ThreadPool
class to be imported automatically into Javaimport qoremod.RestClient.RestClient;
would cause the RestClient
Qore module to be loaded and the RestClient
class to be imported into JavaQore and Python classes imported as above can then be used as Java classes for instantiation or as base classes for Java subclasses.
pythonmod
and 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 namesmark-module-injected
module command; ex: InjectedModule
as non-module symbolsqoremod
and pythonmod
packages will be anchored under this package name even if definitions from other namespaces (from Qore imports) are importedQore and Python functions can be imported using the special $Functions
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.
Qore constants can be imported using the special $Constants
class name; constants in a Qore namespace are imported automatically into a special class with this name as static final
fields in the corresponding dynamic Java package.
You must use the org.qore.jni.QoreURLClassLoader class as the system classloader to run Java bytecode using dynamic imports.
For example:
java -cp jars/my-jar.jar:jars/qore-jni.jar:. -Djava.system.class.loader=org.qore.jni.QoreURLClassLoader MyClass
In case the Qore shared library cannot be found automatically, you may need to set the QORE_LIBRARY
environment variable to the library's location as in the following example:
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
The org.qore.jni.compiler.QoreJavaCompiler class must be used to compile bytecode using dynamic imports.
The standard 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:
And here is a Qore example:
qjavac
helper script can be used to compile Java sources to class files; ex qjavac -cp my-jar.jar MySource.java
qjava2jar
helper script can be used to compile Java sources using dynamic imports to a jar
file; ex: qjava2jar my-jar.jar source_path -cp some-api.jar:another-api.jar -nowarn
This module supports the following compatibility option: "compat-types"
which, when enabled, will disable the following type conversions:
byte
[] to binary
; instead this will converted to list<int>
java.util.Map
to hash
; instead this will be converted directlyThis option can be set globally for all Program objects with set_module_option("jni", "compat-type", True)
or locally for the current Program container with the set-compat-type
module parse option like: module-cmd(jni) set-compat-type true
or module-cmd(jni) set-compat-type false
to override the global setting.
The jni
module uses reflection to automatically map Java classes to Qore classes. This class mapping and Qore class creation happens at parse time (when 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:
java.lang.Object
The following table describes type conversions for Java types when the jni
module must convert a Qore value to the declared type:
Qore to Specific Java Type Conversions
Target Java Type | Source Qore Type |
boolean | any; conversions are made like with boolean() |
byte | any; conversions are made like with int() |
char | any; conversions are made like with int() |
short | any; conversions are made like with int() |
int | any; conversions are made like with int() |
long | any; conversions are made like with int() |
float | any; conversions are made like with float() |
double | any; conversions are made like with float() |
For other types, default conversions are used.
The following table describes type conversions for Java types when the jni
module must convert a Qore value to the declared type:
Qore to Java Default Type Conversions
Source Qore Type | Target Java Type |
bool | boolean |
int | long |
float | double |
string | java.lang.String |
date | java.lang.Object (compile time) or java.time.ZonedDateTime or org.qore.jni.QoreRelativeTime (runtime) |
number | java.math.BigDecimal |
binary | byte [] |
NOTHING and NULL | void |
list | java Arrays of the list type; if no list type can be found, then the Array type is java.lang.Object |
hash | org.qore.jni.Hash (extends java.util.LinkedHashMap ), however any type implementing java.util.Map that has a constructor that takes no arguments and a compatibule put() method will be constructed / converted automatically to the desired type |
code (closure or call reference) | org.qore.jni.QoreClosure |
JavaArray | java.lang.Array |
object | to the Java class if the Qore object is an instantiation of java.lang.Object , otherwise org.qore.jni.QoreObject |
all jni objects | direct conversion |
int
value; use: Java to Qore Type Conversions
Source Java Type | Target Qore Type |
boolean | bool |
byte | int |
byte [] | binary (see also JNI Module Compatibility Options) |
char | int |
short | int |
int | int |
long | int |
float | float |
double | double |
java.lang.String | string |
java.sql.Date | date (absolute date) |
java.sql.Time | date (absolute date based on 1970-01-01 ) |
java.sql.Timestamp | date (absolute date) |
java.time.ZonedDateTime | date (absolute date) |
org.qore.jni.QoreRelativeTime | date (relative date) |
java.math.BigDecimal | number |
java.lang.AbstractArray and arrays | list |
java.util.Map | hash (see also JNI Module Compatibility Options) |
org.qore.jni.QoreClosure | code |
org.qore.jni.QoreClosureMarker | code |
all other objects | direct conversion |
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 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 JavaArray class supplied with the 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.
When a Java method declares variable arguments, such arguments must be generally given as a single array value (so a Qore list or JavaArray object). Consider the following example:
In the above example, the java.lang.Class.getMethod()
and the 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 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:
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 org.qore.jni.QoreObject and 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:
The strong reference to any Qore object returned by the preceding methods is managed in one of two ways described in the following sections.
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 "_jni_save"
thread-local key, if set. If no such key is set, then "_jni_save"
is used instead as the literal key for saving the list of objects.
The lifecycle of Qore objects can be managed explicitly by using the 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:
The Java thread stack size is set when the JVM is initialized in the 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 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.
This module implements the "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 jdbc
DBI driver, use the driver name "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: jdbc:[
user]/[
pass]@
db[{
option=
val[,...]}]
where all elements except @
db are optional.
The host, port, and charset values in Qore datasource strings are ignored; the host and port must be given in the db value, and, if the jdbc driver's URL format requires the use of @
or : characters, then a dummy value must be provided for the db value, and the url option must be used instead.
Some example Qore datasource strings for the jdbc
driver:
jdbc:user/pass@postgresql:dbname{classpath=/usr/share/java/postgresql.jar}
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}
jdbc:user/pass@ignored{url=oracle:thin:@hostname:1521:dbserver,classpath=/usr/lib/oracle/21/client64/lib/ojdbc8.jar}
jdbc:
in the db
value is optional; if missing, it is added automatically by the Qore jdbc
driver when the internal URL string is passed to the internal Java JDBC call.The jdbc driver supports the following DBI options:
"classpath"
: sets the classpath with jar or class files providing the jdbc
driver"numeric-numbers"
: return received SQL_NUMERIC
and SQL_DECIMAL
values as arbitrary-precision numbers (Qore number values)"optimal-numbers"
: return received SQL_NUMERIC
and SQL_DECIMAL
values as integers if possible, if not return them as an arbitrary-precision numbers; this is the default"string-numbers"
: return received SQL_NUMERIC
and SQL_DECIMAL
values as strings (for backwards- compatibility)"url"
: sets the URL for the jdbc
driver in case the database value in the Qore datasource connection string cannot accommodate the jdbc
URL because of special charactersoptimal-numbers
is set on the driver for maximum compatibility with other Qore DBI driversThe classpath
option allows for the classpath to be set before the JVM loads the Java 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 java.util.ServiceLoader
for "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 java.sql.DriverManager
will not see the new drivers as they were not present in the classpath during on-demand static initialization).
The url
option allows for jdbc
URLs to be used that contain special characters like @
and : that would cause the datasource to fail to parse when used as a database name.
Datasource ds("jdbc:user/pass@ignored{url=oracle:thin:@hostname:1521:dbserver,classpath=/usr/lib/oracle/21/client64/lib/ojdbc8.jar}");
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.Float
values (issue 4890)DriverManager.getConnection()
to ensure that the call succeeds (issue 4740)qjavac
did not detect the binary path of compiled objects correctly in all cases (issue 4683)qjavac
where error information was not output when compiler diagnostic information was missing (issue 4682)qpp
from Qore 1.12.4+code
type into Java as org.qore.jni.QoreClosureMarkerImpl types (issue 4593)qjavac
wrote output files in the current working directory instead of in the directory of the input file (issue 4376)$Functions
Java class if the enclosing namespace had less than two classes in it (issue 4375)mark-module-injected
module command was implemented (issue 4373)Program
and Java 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 (issue 4371)'$'
target when importing Qore or Python modules with import statements with the qoremod
or pythonmod
special packages (issue 4304)null
or nothing
would cause runtime errors when importing the method into Java (issue 4287)long
in order to maintain precisiongetAs*
() methods to the org.qore.jni.Hash class to help in retrieving key values with specific base types (issue 3957)java.util.List<?>
instead of java.util.ArrayList<?>
as the base class for list conversions (issue 3873)QORE_JNI_DISABLE_JIT=1
environment variable (issue 3199)set-property
module command to allow for Java system properties to be set while initializing the module (issue 3495)