Call Java methods from Nim using jnim

jnim

jnim is a library that wraps jni api and can call Java from nim. Qiita also has several articles about jnim.

Looking at the original site, I wrote only a sample of System.out.println, and even if I googled how to use a library like that registered in the Maven repository, I can not find an article like that. It was.

This time, it will be an article that explains the sample source for calling Java classes contained in external Jar files from Nim.

import jnim

# Import a couple of classes
jclass java.io.PrintStream of JVMObject:
  proc println(s: string)
jclass java.lang.System of JVMObject:
  proc `out`: PrintStream {.prop, final, `static`.}

# Initialize JVM
initJNI()
# Call!
System.`out`.println("This string is printed with System.out.println!")

Sample source

This sample source is saved on GitHub. https://github.com/6in/jnim-sample

As for the contents of the sample source, refer to the UUID library published in the Maven repository and call the Java class that generates the UUID from Nim to generate the UUID.

https://mvnrepository.com/artifact/com.fasterxml.uuid/java-uuid-generator

JAVA_HOME settings

Please set JAVA_HOME because jnim will check the JAVA_HOME environment variable and get the location of the JVM.

Source overview

File folder Explanation
sample.nimble nimble file
sample.nim Main source
uuid_classes.nim Java Class definition for jnim
gradlew gradle wrapper for *nix
gradlew.bat gradle wapper for windows
build.gradle gradle definition file
jars/ The Jar file you want to use will be copied here

Get Jar using Gradle

Use Gradle (Wrapper) to get the Jar from Maven Central and store the Jar file including the dependencies in one folder.

Add the library you want to use to the dependencies of build.gradle.

build.gradle


/*
 * This file was generated by the Gradle 'init' task.
 *
 * This generated file contains a sample Groovy library project to get you started.
 * For more details take a look at the 'Building Java & JVM projects' chapter in the Gradle
 * User Manual available at https://docs.gradle.org/6.7.1/userguide/building_java_projects.html
 */

plugins {
    id 'java-library'
}

repositories {
    // Use JCenter for resolving dependencies.
    jcenter()
}

//Add the library you want to use
dependencies {
    compile group: 'com.fasterxml.uuid', name: 'java-uuid-generator', version: '4.0.1'
}

//Copy Jar defined in dependencies and related Jar to jars
task collectJars(type: Copy) {
    into "jars"
    from configurations.runtime
  }

When you execute the defined custom task (collectJars), a Jars folder is created and contains two Jar files.

> nimble collectJars
> dir jars
jnim-sample\jars
12/13/2020  02:46 PM    <DIR>          .
12/13/2020  02:46 PM    <DIR>          ..
12/13/2020  02:46 PM            37,457 java-uuid-generator-4.0.1.jar
12/13/2020  02:46 PM            41,424 slf4j-api-1.7.29.jar
               2 File(s)         78,881 bytes
               2 Dir(s)  252,181,385,216 bytes free

Define the class you want to use in Nim

Define the class and method you want to use as follows. For static methods, define {.static.}.

uuid_classes.nim


import jnim

#Definition of Java class to be used(Must be defined one by one)
jclass java.util.UUID * of JVMObject:
  proc toString*(): string

jclass com.fasterxml.uuid.impl.RandomBasedGenerator * of JVMObject:
  proc generate*(): UUID

jclass com.fasterxml.uuid.impl.NameBasedGenerator * of JVMObject:
  proc generate*(name: string): UUID

jclass com.fasterxml.uuid.Generators * of JVMObject:
  proc randomBasedGenerator*(): RandomBasedGenerator {.`static`.}
  proc nameBasedGenerator*(): NameBasedGenerator {.`static`.}

Initialize & call Java in Nim

In Java, when calling a class in another Jar, it is necessary to set the path to the Jar file in the environment variable CLASSPATH or set the Java startup option CLASSPATH, but jnim Since it does not refer to the environment variable CLASSPATH, it must be passed as an option to the argument of the JVM initialization function initJNI.

Specifically, specify the path to Jar in the format -Djava.class.path =/path/to/jar. In the sample below, get the Jar file stored in the jars folder, assemble the string concatenated with the file separator, and call initJNI.

The generated UUID is output by calling the static method of the Generators class defined in uuid_classes.nim.

sample.nim


import os
import jnim
import strutils
import ./uuid_classes

# jars/Enumerate the jar files contained in
var paths: seq[string] = @[]
for f in walkDir("jars"):
  paths.add f.path

when defined(windows):
  const sep = ";"
else:
  const sep = ":"

# Initialize JVM with JVMOption
initJNI(JNIVersion.v1_8, @["-Djava.class.path=" & paths.join(sep)])

# call method
var randomBased = Generators.randomBasedGenerator().generate().toString()
var nameBased = Generators.nameBasedGenerator().generate("name").toString()

echo fmt"{ randomBased = }"
echo fmt"{ nameBased   = }"

Output result

The SLF4J log is displayed, but it was confirmed that both ramdomBased and nameBased were acquired.

> nimble run
  Verifying dependencies for [email protected]
      Info: Dependency on jnim@>= 0.5.1 already satisfied
  Verifying dependencies for [email protected]
   Building sample/sample.exe using c backend
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
 randomBased = 7a95887d-b10b-4008-9588-7db10bc008e2
 nameBased   = 6ae99955-2a0d-5dca-94d6-2e2bc8b764d3

Call from Nim thread

When calling a Java method from a Nim thread, I had to call initJNIThread at the beginning of the threading process to attach it to the already initialized JVM.

#Call from thread
proc threadFunc(param: tuple[a, b: int]) {.thread, gcsafe.} = 
  #This call is absolutely necessary(Attach to a VM that has already been initialized)
  initJNIThread()
  defer:
    #Call at the end
    deinitJNIThread()
  var randomBased = ""
  var nameBased = ""
  for x in param.a..param.b:
    randomBased = Generators.randomBasedGenerator().generate().toString()
    nameBased = Generators.nameBasedGenerator().generate("name").toString()
  echo fmt"{ randomBased = }"
  echo fmt"{ nameBased   = }"

block:
  defer:
    echo "end threads"

  var thr: array[0..1, Thread[tuple[a, b: int]]]
  echo "start threads"
  thr[0].createThread(threadFunc, (1, 1000))
  thr[1].createThread(threadFunc, (1, 1000))
  sleep(1000)
  echo "wait threads"
  joinThreads(thr)

For build, specify the threads switch and threadAnalysis switch.

reference: I translated the Nim manual into Japanese https://qiita.com/tauplus/items/80afbd47f3a44158ea1f

nim c --threads:on --threadAnalysis:off -r sample

Output result

start threads
 randomBased = c0466bea-6769-49c1-866b-ea6769e9c170
 nameBased   = 6ae99955-2a0d-5dca-94d6-2e2bc8b764d3
 randomBased = 9032c0ad-1afc-4bfe-b2c0-ad1afc4bfe47
 nameBased   = 6ae99955-2a0d-5dca-94d6-2e2bc8b764d3
wait threads
end threads

Summary

I created a sample that uses an existing Java library using jnim. As a feature,

Impressions

It is troublesome to write the definition of the Java class you want to use one by one, so I thought it would be nice to have something like c2nim that automatically creates a definition file from the Java class. ..

Also, even if there is a library you want to use, not all functions are required, so it is good to create a Gradle library project, define a simple class that wraps the library, and call it from Nim. I feel like that. (By the way, it would be better to make it Fat Jar)

But well, if you want to use Java classes up to that point, you can create Java/Groovy/Kotlin/Scala/Clojure.

bonus

Since there is a jexport macro that can create a Java class implementation in Nim, it seems that it can also handle callbacks from Java.

type
  MyObjData = ref object
    a: int

  MyObj = ref object of JVMObject
    data: MyObjData

jexport MyObjSub extends MyObj:
  proc new = super()

  proc stringMethod(): string =
    "Nim"

Recommended Posts

Call Java methods from Nim using jnim
Call Java from JRuby
Using Docker from Java Gradle
Call Kotlin's sealed class from Java
Call TensorFlow Java API from Scala
Sample code using Minio from Java
Java methods
Java methods
Call Java library from C with JNI
Call GitHub API from Java Socket API part2
Using JavaScript from Java in Rhino 2021 version
Connect from Java to MySQL using Eclipse
Try calling Nim from Java via JNI
Access Forec.com from Java using Axis2 Enterprise WSDL
[Android] Call Kotlin's default argument method from Java
Java class methods
Java method call from RPG (method call in own class)
Ssh connect using SSHJ from a Java 6 app
Try calling synchronized methods from multiple threads in Java
Output the maximum value from the array using Java standard output
Behavior when calling Java variadic methods from Scala / Kotlin / Java
Call a method with a Kotlin callback block from Java
Using the database (SQL Server 2014) from a Java program 2018/01/04
Call a program written in Swift from Processing (Java)
Changes from Java 8 to Java 11
Sum from Java_1 to 100
Try using java.lang.Math methods
Eval Java source from Java
Access API.AI from Java
From Java to Ruby !!
JAVA constructor call processing
Implement Java Interface in JRuby class and call it from Java
[Kotlin] Get Java Constructor / Method from KFunction and call it