Skip to content

JVM targets, primitive arrays, sealed value hierarchies and default arguments - #1

Merged
kdroidFilter merged 5 commits into
mainfrom
feat/jvm-target
Oct 17, 2026
Merged

kdroidFilter merged 5 commits into
mainfrom
feat/jvm-target

Conversation

@kdroidFilter

Copy link
Copy Markdown
Contributor

Summary

What Zayit's search needs to use NJA for Lucene on every platform (kdroidFilter/SeforimLibrary#134, kdroidFilter/Zayit#698), as general NJA features.

  • jvm() targets (a desktop app): nja-client gets a JVM target whose transport calls the server's dispatcher on the calling thread. Callbacks are served back on that thread, as on iOS but without JNI. The plugin puts the façades on the target's runtime class path. The app's code is therefore the same on every platform, and the messages are still encoded.
    • Both the façades and their proxies are on that class path, so the proxies need a package of their own (njaFacade.clientPackage).
    • The generation writes nja-packages.properties, and a checkNja<Target>ClientPackages task fails clearly before the JVM target compiles if the packages collide.
  • Primitive arrays: IntArray, LongArray, FloatArray and DoubleArray cross as a count and their elements.
  • Sealed value hierarchies: a sealed interface or class whose nested cases are all data classes or objects travels by value (the case's index, then its fields), copied verbatim like a data class.
    • Value types now reference each other by name, so they can be recursive (a sealed value holding a list of itself, through a data class).
  • Default arguments: a method's or companion's trailing default parameters get overloads, as constructors already did; the JVM applies the façade's defaults.
  • AutoCloseable façades: their close() is generated as the proxy's override fun close(). It closes the JVM object, then releases it, once. It used to be generated as a member hiding JvmObject.close(), which didn't compile.
  • Only the public API is exported:
    • Private and internal declarations stay out of the symbol table, and private constructor properties out of the API.
    • A class whose constructor is private, internal, or takes a type that can't cross isn't constructible from the app.
    • A function with a parameter or result that can't cross is left out. Before, the parameter was silently dropped and the result taken as Unit.
  • examples/calculator:
    • It gets a jvm() target running the same suites.
    • Its proxies move into their own packages, and so do the tests.
    • New tests cover arrays, sealed values, AutoCloseable, internal constructors and default arguments.

Verification

examples/calculator: 2,008 tests pass on all three platforms: jvmTest, iosSimulatorArm64Test (jdk-mobile-27b35.3) and connectedAndroidDeviceTest (Android 11 emulator).

Notes

  • Example packages: the calculator and torture façades now set clientPackage, so the example's tests are in com.example.calculator.client. Other consumers keep the default package unless they add a JVM target.
  • Downstream modules: a KMP module using a module that applies NJA (Zayit's searchkit uses SeforimLibrary's search) gets Android for free through the library's AAR. Its iOS binaries, though, still have to link the runtime and copy NJA's bundle by hand. Supporting that case in the plugin would be a natural follow-up.

- A Kotlin Multiplatform module's jvm() target runs the façades in its own JVM: nja-client gets a jvm target
  whose transport calls the server's dispatcher on the calling thread (callbacks served back on it, as on iOS
  without JNI); the plugin puts the façades on the target's runtime class path and checks that their proxies
  have a package of their own (njaFacade.clientPackage), as both are on it.
- IntArray, LongArray, FloatArray and DoubleArray cross the bridge as a count and their elements.
- A sealed interface or class whose nested cases are all data classes or objects travels by value (its case,
  then the case's fields), copied verbatim like a data class; value types now reference each other by name,
  so they may be recursive (a sealed value holding a list of itself, through a data class...).
- examples/calculator: a jvm target running the same suites, the proxies in their own packages, tests for
  the arrays and the sealed values.

Signed-off-by: Elie Gambache <elyahou.hadass@gmail.com>
- A façade's close() is the proxy's: it calls the JVM object's close(), then releases it, once (it was
  generated as a member hiding JvmObject's).
- Private and internal declarations are left out of the symbol table, private constructor properties out
  of the API, and a class whose constructor is private, internal or takes what can't cross the bridge isn't
  constructible from the app (its constructor's unsupported parameters were dropped).

Signed-off-by: Elie Gambache <elyahou.hadass@gmail.com>
A method's (or a companion's) trailing parameters with default values get overloads, as constructors do: the
app leaves them out, the JVM applies the façade's defaults. A function with a parameter or a result that
can't cross the bridge is left out (the parameter was dropped, the result taken for Unit).

Signed-off-by: Elie Gambache <elyahou.hadass@gmail.com>
Signed-off-by: Elie Gambache <elyahou.hadass@gmail.com>
The test names also have to be valid DEX and Kotlin/Native names (no comma, no apostrophe).

Signed-off-by: Elie Gambache <elyahou.hadass@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant