Skip to content

Commit

Permalink
Migrate to a dedicated "Jni" type (#644)
Browse files Browse the repository at this point in the history
This will aid in future migration to an abstraction which can better support Windows.
  • Loading branch information
JakeWharton authored Jan 14, 2025
1 parent c5f4fb0 commit 387bbff
Show file tree
Hide file tree
Showing 4 changed files with 120 additions and 117 deletions.
22 changes: 11 additions & 11 deletions mosaic-terminal/src/jvmMain/jni/mosaic-jni.c
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ void throwIse(JNIEnv *env, unsigned int error, const char *prefix) {
}

JNIEXPORT jlong JNICALL
Java_com_jakewharton_mosaic_terminal_Tty_enterRawMode(JNIEnv *env, jclass type) {
Java_com_jakewharton_mosaic_terminal_Jni_enterRawMode(JNIEnv *env, jclass type) {
rawModeResult result = enterRawMode();
if (likely(!result.error)) {
return (jlong) result.saved;
Expand All @@ -38,15 +38,15 @@ Java_com_jakewharton_mosaic_terminal_Tty_enterRawMode(JNIEnv *env, jclass type)
}

JNIEXPORT void JNICALL
Java_com_jakewharton_mosaic_terminal_Tty_exitRawMode(JNIEnv *env, jclass type, jlong ptr) {
Java_com_jakewharton_mosaic_terminal_Jni_exitRawMode(JNIEnv *env, jclass type, jlong ptr) {
platformError error = exitRawMode((rawModeConfig *) ptr);
if (unlikely(error)) {
throwIse(env, error, "Unable to exit raw mode");
}
}

JNIEXPORT jlong JNICALL
Java_com_jakewharton_mosaic_terminal_Tty_stdinReaderInit(JNIEnv *env, jclass type) {
Java_com_jakewharton_mosaic_terminal_Jni_stdinReaderInit(JNIEnv *env, jclass type) {
stdinReaderResult result = stdinReader_init();
if (likely(!result.error)) {
return (jlong) result.reader;
Expand All @@ -59,7 +59,7 @@ Java_com_jakewharton_mosaic_terminal_Tty_stdinReaderInit(JNIEnv *env, jclass typ
}

JNIEXPORT jint JNICALL
Java_com_jakewharton_mosaic_terminal_Tty_stdinReaderRead(
Java_com_jakewharton_mosaic_terminal_Jni_stdinReaderRead(
JNIEnv *env,
jclass type,
jlong ptr,
Expand All @@ -85,7 +85,7 @@ Java_com_jakewharton_mosaic_terminal_Tty_stdinReaderRead(
}

JNIEXPORT jint JNICALL
Java_com_jakewharton_mosaic_terminal_Tty_stdinReaderReadWithTimeout(
Java_com_jakewharton_mosaic_terminal_Jni_stdinReaderReadWithTimeout(
JNIEnv *env,
jclass type,
jlong ptr,
Expand Down Expand Up @@ -117,23 +117,23 @@ Java_com_jakewharton_mosaic_terminal_Tty_stdinReaderReadWithTimeout(
}

JNIEXPORT void JNICALL
Java_com_jakewharton_mosaic_terminal_Tty_stdinReaderInterrupt(JNIEnv *env, jclass type, jlong ptr) {
Java_com_jakewharton_mosaic_terminal_Jni_stdinReaderInterrupt(JNIEnv *env, jclass type, jlong ptr) {
platformError error = stdinReader_interrupt((stdinReader *) ptr);
if (unlikely(error)) {
throwIse(env, error, "Unable to interrupt stdin reader");
}
}

JNIEXPORT void JNICALL
Java_com_jakewharton_mosaic_terminal_Tty_stdinReaderFree(JNIEnv *env, jclass type, jlong ptr) {
Java_com_jakewharton_mosaic_terminal_Jni_stdinReaderFree(JNIEnv *env, jclass type, jlong ptr) {
platformError error = stdinReader_free((stdinReader *) ptr);
if (unlikely(error)) {
throwIse(env, error, "Unable to free stdin reader");
}
}

JNIEXPORT jlong JNICALL
Java_com_jakewharton_mosaic_terminal_Tty_stdinWriterInit(JNIEnv *env, jclass type) {
Java_com_jakewharton_mosaic_terminal_Jni_stdinWriterInit(JNIEnv *env, jclass type) {
stdinWriterResult result = stdinWriter_init();
if (likely(!result.error)) {
return (jlong) result.writer;
Expand All @@ -146,7 +146,7 @@ Java_com_jakewharton_mosaic_terminal_Tty_stdinWriterInit(JNIEnv *env, jclass typ
}

JNIEXPORT void JNICALL
Java_com_jakewharton_mosaic_terminal_Tty_stdinWriterWrite(
Java_com_jakewharton_mosaic_terminal_Jni_stdinWriterWrite(
JNIEnv *env,
jclass type,
jlong ptr,
Expand All @@ -166,12 +166,12 @@ Java_com_jakewharton_mosaic_terminal_Tty_stdinWriterWrite(
}

JNIEXPORT jlong JNICALL
Java_com_jakewharton_mosaic_terminal_Tty_stdinWriterGetReader(JNIEnv *env, jclass type, jlong ptr) {
Java_com_jakewharton_mosaic_terminal_Jni_stdinWriterGetReader(JNIEnv *env, jclass type, jlong ptr) {
return (jlong) stdinWriter_getReader((stdinWriter *) ptr);
}

JNIEXPORT void JNICALL
Java_com_jakewharton_mosaic_terminal_Tty_stdinWriterFree(JNIEnv *env, jclass type, jlong ptr) {
Java_com_jakewharton_mosaic_terminal_Jni_stdinWriterFree(JNIEnv *env, jclass type, jlong ptr) {
platformError error = stdinWriter_free((stdinWriter *) ptr);
if (unlikely(error)) {
throwIse(env, error, "Unable to free stdin writer");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package com.jakewharton.mosaic.terminal

import java.io.IOException
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.StandardCopyOption.REPLACE_EXISTING
import java.util.Locale.US

// TODO @JvmSynthetic https://youtrack.jetbrains.com/issue/KT-24981
internal object Jni {
init {
loadNativeLibrary("mosaic")
}

@JvmStatic
external fun enterRawMode(): Long

@JvmStatic
external fun exitRawMode(savedConfig: Long)

@JvmStatic
external fun stdinReaderInit(): Long

@JvmStatic
external fun stdinReaderRead(
reader: Long,
buffer: ByteArray,
offset: Int,
count: Int,
): Int

@JvmStatic
external fun stdinReaderReadWithTimeout(
reader: Long,
buffer: ByteArray,
offset: Int,
count: Int,
timeoutMillis: Int,
): Int

@JvmStatic
external fun stdinReaderInterrupt(reader: Long)

@JvmStatic
external fun stdinReaderFree(reader: Long)

@JvmStatic
external fun stdinWriterInit(): Long

@JvmStatic
external fun stdinWriterGetReader(writer: Long): Long

@JvmStatic
external fun stdinWriterWrite(writer: Long, buffer: ByteArray)

@JvmStatic
external fun stdinWriterFree(writer: Long)

@Suppress(
// Only loading from our own JAR contents.
"UnsafeDynamicallyLoadedCode",
// Preserving copy/paste!
"SameParameterValue",
)
private fun loadNativeLibrary(name: String) {
val osName = System.getProperty("os.name").lowercase(US)
val osArch = System.getProperty("os.arch").lowercase(US)
val nativeLibraryJarPath = "/jni/$osArch/" + when {
"linux" in osName -> "lib$name.so"
"mac" in osName -> "lib$name.dylib"
"windows" in osName -> "$name.dll"
else -> throw IllegalStateException("Unsupported OS: $osName $osArch")
}
val nativeLibraryUrl = Tty::class.java.getResource(nativeLibraryJarPath)
?: throw IllegalStateException("Unable to read $nativeLibraryJarPath from JAR")
val nativeLibraryFile: Path
try {
nativeLibraryFile = Files.createTempFile(name, null)

// File-based deleteOnExit() uses a special internal shutdown hook that always runs last.
nativeLibraryFile.toFile().deleteOnExit()
nativeLibraryUrl.openStream().use { nativeLibrary ->
Files.copy(nativeLibrary, nativeLibraryFile, REPLACE_EXISTING)
}
} catch (e: IOException) {
throw RuntimeException("Unable to extract native library from JAR", e)
}
System.load(nativeLibraryFile.toAbsolutePath().toString())
}
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
package com.jakewharton.mosaic.terminal

import java.io.IOException
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.StandardCopyOption.REPLACE_EXISTING
import java.util.Locale.US
import com.jakewharton.mosaic.terminal.Jni.enterRawMode
import com.jakewharton.mosaic.terminal.Jni.exitRawMode
import com.jakewharton.mosaic.terminal.Jni.stdinReaderFree
import com.jakewharton.mosaic.terminal.Jni.stdinReaderInit
import com.jakewharton.mosaic.terminal.Jni.stdinReaderInterrupt
import com.jakewharton.mosaic.terminal.Jni.stdinReaderRead
import com.jakewharton.mosaic.terminal.Jni.stdinReaderReadWithTimeout
import com.jakewharton.mosaic.terminal.Jni.stdinWriterFree
import com.jakewharton.mosaic.terminal.Jni.stdinWriterGetReader
import com.jakewharton.mosaic.terminal.Jni.stdinWriterInit
import com.jakewharton.mosaic.terminal.Jni.stdinWriterWrite

public actual object Tty {
init {
loadNativeLibrary("mosaic")
}

@JvmStatic
public actual fun enableRawMode(): AutoCloseable {
val savedConfig = enterRawMode()
Expand Down Expand Up @@ -40,113 +42,25 @@ public actual object Tty {
val reader = stdinWriterGetReader(writer)
return StdinWriter(writer, reader)
}

@JvmStatic
private external fun enterRawMode(): Long

@JvmStatic
private external fun exitRawMode(savedConfig: Long)

@JvmStatic
private external fun stdinReaderInit(): Long

@JvmStatic
@JvmSynthetic // Hide from Java callers.
@JvmName("stdinReaderRead") // Avoid internal name mangling.
internal external fun stdinReaderRead(
reader: Long,
buffer: ByteArray,
offset: Int,
count: Int,
): Int

@JvmStatic
@JvmSynthetic // Hide from Java callers.
@JvmName("stdinReaderReadWithTimeout") // Avoid internal name mangling.
internal external fun stdinReaderReadWithTimeout(
reader: Long,
buffer: ByteArray,
offset: Int,
count: Int,
timeoutMillis: Int,
): Int

@JvmStatic
@JvmSynthetic // Hide from Java callers.
@JvmName("stdinReaderInterrupt") // Avoid internal name mangling.
internal external fun stdinReaderInterrupt(reader: Long)

@JvmStatic
@JvmSynthetic // Hide from Java callers.
@JvmName("stdinReaderFree") // Avoid internal name mangling.
internal external fun stdinReaderFree(reader: Long)

@JvmStatic
private external fun stdinWriterInit(): Long

@JvmStatic
private external fun stdinWriterGetReader(writer: Long): Long

@JvmStatic
@JvmSynthetic // Hide from Java callers.
@JvmName("stdinWriterWrite") // Avoid internal name mangling.
internal external fun stdinWriterWrite(writer: Long, buffer: ByteArray)

@JvmStatic
@JvmSynthetic // Hide from Java callers.
@JvmName("stdinWriterFree") // Avoid internal name mangling.
internal external fun stdinWriterFree(writer: Long)

@Suppress(
// Only loading from our own JAR contents.
"UnsafeDynamicallyLoadedCode",
// Preserving copy/paste!
"SameParameterValue",
)
private fun loadNativeLibrary(name: String) {
val osName = System.getProperty("os.name").lowercase(US)
val osArch = System.getProperty("os.arch").lowercase(US)
val nativeLibraryJarPath = "/jni/$osArch/" + when {
"linux" in osName -> "lib$name.so"
"mac" in osName -> "lib$name.dylib"
"windows" in osName -> "$name.dll"
else -> throw IllegalStateException("Unsupported OS: $osName $osArch")
}
val nativeLibraryUrl = Tty::class.java.getResource(nativeLibraryJarPath)
?: throw IllegalStateException("Unable to read $nativeLibraryJarPath from JAR")
val nativeLibraryFile: Path
try {
nativeLibraryFile = Files.createTempFile(name, null)

// File-based deleteOnExit() uses a special internal shutdown hook that always runs last.
nativeLibraryFile.toFile().deleteOnExit()
nativeLibraryUrl.openStream().use { nativeLibrary ->
Files.copy(nativeLibrary, nativeLibraryFile, REPLACE_EXISTING)
}
} catch (e: IOException) {
throw RuntimeException("Unable to extract native library from JAR", e)
}
System.load(nativeLibraryFile.toAbsolutePath().toString())
}
}

public actual class StdinReader internal constructor(
private val readerPtr: Long,
) : AutoCloseable {
public actual fun read(buffer: ByteArray, offset: Int, count: Int): Int {
return Tty.stdinReaderRead(readerPtr, buffer, offset, count)
return stdinReaderRead(readerPtr, buffer, offset, count)
}

public actual fun readWithTimeout(buffer: ByteArray, offset: Int, count: Int, timeoutMillis: Int): Int {
return Tty.stdinReaderReadWithTimeout(readerPtr, buffer, offset, count, timeoutMillis)
return stdinReaderReadWithTimeout(readerPtr, buffer, offset, count, timeoutMillis)
}

public actual fun interrupt() {
Tty.stdinReaderInterrupt(readerPtr)
stdinReaderInterrupt(readerPtr)
}

public actual override fun close() {
Tty.stdinReaderFree(readerPtr)
stdinReaderFree(readerPtr)
}
}

Expand All @@ -158,10 +72,10 @@ internal actual class StdinWriter internal constructor(
actual val reader: StdinReader = StdinReader(readerPtr)

actual fun write(buffer: ByteArray) {
Tty.stdinWriterWrite(writerPtr, buffer)
stdinWriterWrite(writerPtr, buffer)
}

actual override fun close() {
Tty.stdinWriterFree(writerPtr)
stdinWriterFree(writerPtr)
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
# Keep native method names which are used by the consumer. Our JNI code only creates JDK types and
# only uses Java built-in types across the boundary.
-keepclasseswithmembernames class com.jakewharton.mosaic.terminal.** {
# Note: Our JNI code only creates JDK types and only uses Java built-in types across the boundary.
-keep,allowoptimization class com.jakewharton.mosaic.terminal.Jni {
native <methods>;
}

0 comments on commit 387bbff

Please sign in to comment.