aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAhmed El Khazari <ahmed.el.khazari@qt.io>2025-01-31 16:35:16 +0200
committerVille Voutilainen <ville.voutilainen@qt.io>2025-04-15 05:55:01 +0000
commite0bc3797f469d32004ab1f8401cd4b51c4bce6fd (patch)
tree19d9b0491d5ba94d355babbc89dd5223184dcc43
parent5e2e5625799b7f1152daf8412555677830436fdb (diff)
Introduce utils package
This package is introduced to support upcoming patches, including template providers and header/source generation. It serves as a utility toolbox across other packages by providing helper classes and extension functions. Changes: - Moved AnnotationResolver for handling annotation-based logic - Moved Constants to store shared constants - Added CppClass to assist with C++ code generation - Created FieldSetterGetterFinder to identify field accessors. - Added FileHandler for file management operations along JennyFileException and its Handler - Introduced JennyNameProvider and NamespaceHelper for name and namespace resolution - Implemented ParametersProvider to manage function parameters - Added Signature to handle signature generation - Introduced ext.kt for extension functions Task-number: QTTA-271 Change-Id: I9149211ba244adb7303c0a1c626e4ff1f8ff465c Reviewed-by: Ville Voutilainen <ville.voutilainen@qt.io>
-rw-r--r--compiler/src/main/java/io/github/landerlyoung/jenny/exception/FileExceptionHandler.kt64
-rw-r--r--compiler/src/main/java/io/github/landerlyoung/jenny/exception/JennyFileException.kt71
-rw-r--r--compiler/src/main/java/io/github/landerlyoung/jenny/utils/AnnotationResolver.kt16
-rw-r--r--compiler/src/main/java/io/github/landerlyoung/jenny/utils/Constants.kt53
-rw-r--r--compiler/src/main/java/io/github/landerlyoung/jenny/utils/CppClass.kt18
-rw-r--r--compiler/src/main/java/io/github/landerlyoung/jenny/utils/FieldSetterGetterFinder.kt64
-rw-r--r--compiler/src/main/java/io/github/landerlyoung/jenny/utils/FileHandler.kt99
-rw-r--r--compiler/src/main/java/io/github/landerlyoung/jenny/utils/JennyNameProvider.kt30
-rw-r--r--compiler/src/main/java/io/github/landerlyoung/jenny/utils/NamespaceHelper.kt42
-rw-r--r--compiler/src/main/java/io/github/landerlyoung/jenny/utils/ParametersProvider.kt130
-rw-r--r--compiler/src/main/java/io/github/landerlyoung/jenny/utils/Signature.kt74
-rw-r--r--compiler/src/main/java/io/github/landerlyoung/jenny/utils/ext.kt112
12 files changed, 773 insertions, 0 deletions
diff --git a/compiler/src/main/java/io/github/landerlyoung/jenny/exception/FileExceptionHandler.kt b/compiler/src/main/java/io/github/landerlyoung/jenny/exception/FileExceptionHandler.kt
new file mode 100644
index 0000000..802a4a9
--- /dev/null
+++ b/compiler/src/main/java/io/github/landerlyoung/jenny/exception/FileExceptionHandler.kt
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2025 The Qt Company Ltd.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.github.landerlyoung.jenny.exception
+
+import java.io.File
+import java.io.FileNotFoundException
+import java.io.IOException
+import java.nio.file.InvalidPathException
+
+internal class FileExceptionHandler(private val file: File) : ExceptionHandler {
+ override fun handle(exception: Exception): Exception = when (exception) {
+ is FileNotFoundException -> JennyFileException.FileNotFound(
+ path = file.path,
+ details = exception.message,
+ cause = exception
+ )
+
+ is SecurityException -> JennyFileException.AccessDenied(
+ path = file.path,
+ details = exception.message,
+ cause = exception
+ )
+
+ is InvalidPathException -> JennyFileException.InvalidPath(
+ path = file.path,
+ details = exception.message,
+ cause = exception
+ )
+
+ is IOException -> when (exception) {
+ is FileSystemException -> JennyFileException.OperationFailed(
+ path = file.path,
+ operation = resolveFileOperation(exception),
+ details = "File system error: ${exception.reason ?: "Unknown"}",
+ cause = exception
+ )
+
+ else -> JennyFileException.OperationFailed(
+ path = file.path,
+ operation = "I/O Operation",
+ details = "${exception::class.simpleName}: ${exception.message}",
+ cause = exception
+ )
+ }
+
+ else -> JennyFileException.OperationFailed(
+ path = file.path,
+ operation = "Unknown",
+ details = "Unexpected error: ${exception::class.simpleName}",
+ cause = exception
+ )
+ }
+
+ private fun resolveFileOperation(exception: FileSystemException): String {
+ return when {
+ exception.message?.contains("delete") == true -> "Delete"
+ exception.message?.contains("write") == true || exception.message?.contains("create") == true -> "Write"
+ else -> "Read"
+ }
+ }
+}
diff --git a/compiler/src/main/java/io/github/landerlyoung/jenny/exception/JennyFileException.kt b/compiler/src/main/java/io/github/landerlyoung/jenny/exception/JennyFileException.kt
new file mode 100644
index 0000000..ebb93b4
--- /dev/null
+++ b/compiler/src/main/java/io/github/landerlyoung/jenny/exception/JennyFileException.kt
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2025 The Qt Company Ltd.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.github.landerlyoung.jenny.exception
+
+/**
+ * Base class for file-related exceptions.
+ * Provides structured exception handling for file operations with detailed error messages.
+ *
+ * @property path The file path where the error occurred
+ * @property message The error message
+ * @property details Additional context about the error (optional)
+ * @property cause The underlying cause of the exception
+ */
+internal sealed class JennyFileException private constructor(
+ path: String,
+ message: String,
+ details: String? = null,
+ cause: Throwable? = null
+) : JennyException(buildFileMessage(path, message, details), cause) {
+
+ companion object {
+ private fun buildFileMessage(path: String, message: String, details: String?) =
+ buildString {
+ append("File [$path]: $message")
+ if (!details.isNullOrBlank())
+ append(" - $details")
+ }
+ }
+
+ /**
+ * Thrown when attempting to access a file that does not exist.
+ */
+ class FileNotFound(
+ path: String,
+ details: String? = null,
+ cause: Throwable? = null
+ ) : JennyFileException(path, "Not found", details, cause)
+
+ /**
+ * Thrown when file access is denied due to permissions or security restrictions.
+ */
+ class AccessDenied(
+ path: String,
+ details: String? = null,
+ cause: Throwable? = null
+ ) : JennyFileException(path, "Access denied", details, cause)
+
+ /**
+ * Thrown when a file operation fails.
+ * Provides context about the specific operation that failed such as
+ * Read, Write, Delete...
+ */
+ class OperationFailed(
+ path: String,
+ operation: String,
+ details: String? = null,
+ cause: Throwable? = null
+ ) : JennyFileException(path, "$operation failed", details, cause)
+
+ /**
+ * Thrown when a file path is invalid or malformed.
+ */
+ class InvalidPath(
+ path: String,
+ details: String? = null,
+ cause: Throwable? = null
+ ) : JennyFileException(path, "Invalid path", details, cause)
+}
diff --git a/compiler/src/main/java/io/github/landerlyoung/jenny/utils/AnnotationResolver.kt b/compiler/src/main/java/io/github/landerlyoung/jenny/utils/AnnotationResolver.kt
new file mode 100644
index 0000000..d35bf97
--- /dev/null
+++ b/compiler/src/main/java/io/github/landerlyoung/jenny/utils/AnnotationResolver.kt
@@ -0,0 +1,16 @@
+/*
+ * Copyright 2016 landerlyoung@gmail.com
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.github.landerlyoung.jenny.utils
+
+import java.lang.reflect.Proxy
+
+object AnnotationResolver {
+ @Suppress("UNCHECKED_CAST")
+ fun <T : Annotation> getDefaultImplementation(annotation: Class<T>): T =
+ Proxy.newProxyInstance(annotation.classLoader, arrayOf<Class<*>>(annotation)) { _, method, _ ->
+ method.defaultValue
+ } as T
+}
diff --git a/compiler/src/main/java/io/github/landerlyoung/jenny/utils/Constants.kt b/compiler/src/main/java/io/github/landerlyoung/jenny/utils/Constants.kt
new file mode 100644
index 0000000..2098e3d
--- /dev/null
+++ b/compiler/src/main/java/io/github/landerlyoung/jenny/utils/Constants.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2016 landerlyoung@gmail.com
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package io.github.landerlyoung.jenny.utils
+
+object Constants {
+ private const val JENNY_GEN_DIR = "jenny"
+
+ const val JENNY_GEN_DIR_PROXY = "$JENNY_GEN_DIR.proxy"
+
+ const val JENNY_GEN_DIR_GLUE_SOURCE = "$JENNY_GEN_DIR.glue.cpp"
+
+ const val JENNY_GEN_DIR_GLUE_HEADER = "$JENNY_GEN_DIR.glue.header"
+
+ const val JENNY_FUSION_PROXY_HEADER_NAME = "jenny_fusion_proxies.h"
+
+ const val JENNY_JNI_HELPER_H_NAME = "jnihelper.h"
+
+ val JENNY_JNI_HELPER_H_CONTENT: String
+ get() = Constants.javaClass.classLoader.getResourceAsStream(JENNY_JNI_HELPER_H_NAME)!!.use {
+ String(it.readBytes(), Charsets.UTF_8)
+ }
+
+ const val AUTO_GENERATE_NOTICE = """/**
+ * File generated by Jenny -- https://github.com/LanderlYoung/Jenny
+ *
+ * DO NOT EDIT THIS FILE.
+ *
+ * For bug report, please refer to github issue tracker https://github.com/LanderlYoung/Jenny/issues.
+ */
+"""
+
+ const val AUTO_GENERATE_SOURCE_NOTICE = """/**
+ * File generated by Jenny -- https://github.com/LanderlYoung/Jenny
+ *
+ * For bug report, please refer to github issue tracker https://github.com/LanderlYoung/Jenny/issues.
+ */
+"""
+ // https://en.cppreference.com/w/cpp/keyword
+ val CPP_RESERVED_WORS = setOf(
+ "alignas", "alignof", "and", "and_eq", "asm", "atomic_cancel", "atomic_commit", "atomic_noexcept", "auto",
+ "bitand", "bitor", "bool", "break", "case", "catch", "char", "char8_t", "char16_t", "char32_t", "class",
+ "compl", "concept", "const", "consteval", "constexpr", "constinit", "const_cast", "continue", "co_await",
+ "co_return", "co_yield", "decltype", "default", "delete", "do", "double", "dynamic_cast", "else", "enum",
+ "explicit", "export", "extern", "false", "float", "for", "friend", "goto", "if", "inline", "int", "long",
+ "mutable", "namespace", "new", "noexcept", "not", "not_eq", "nullptr", "operator", "or", "or_eq", "private",
+ "protected", "public", "reflexpr", "register", "reinterpret_cast", "requires", "return", "short", "signed",
+ "sizeof", "static", "static_assert", "static_cast", "struct", "switch", "synchronized", "template", "this",
+ "thread_local", "throw", "true", "try", "typedef", "typeid", "typename", "union", "unsigned", "using",
+ "virtual", "void", "volatile", "wchar_t", "while", "xor", "xor_e"
+ )
+}
diff --git a/compiler/src/main/java/io/github/landerlyoung/jenny/utils/CppClass.kt b/compiler/src/main/java/io/github/landerlyoung/jenny/utils/CppClass.kt
new file mode 100644
index 0000000..9918a1f
--- /dev/null
+++ b/compiler/src/main/java/io/github/landerlyoung/jenny/utils/CppClass.kt
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2020 taylorcyang@tencent.com
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.github.landerlyoung.jenny.utils
+
+data class CppClass(
+ val name: String,
+ val namespace: String,
+ val headerFileName: String
+) : Comparable<CppClass> {
+
+ override fun compareTo(other: CppClass) = compareValuesBy(
+ this, other,
+ { it.namespace },
+ { it.name })
+}
diff --git a/compiler/src/main/java/io/github/landerlyoung/jenny/utils/FieldSetterGetterFinder.kt b/compiler/src/main/java/io/github/landerlyoung/jenny/utils/FieldSetterGetterFinder.kt
new file mode 100644
index 0000000..9747397
--- /dev/null
+++ b/compiler/src/main/java/io/github/landerlyoung/jenny/utils/FieldSetterGetterFinder.kt
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2025 The Qt Company Ltd.
+ * Copyright 2016 landerlyoung@gmail.com
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.github.landerlyoung.jenny.utils
+
+import io.github.landerlyoung.jenny.element.JennyElement
+import io.github.landerlyoung.jenny.element.method.JennyExecutableElement
+
+import java.util.EnumSet
+
+object FieldSetterGetterFinder {
+
+ fun hasGetterSetter(
+ field: JennyElement,
+ allMethods: Collection<JennyExecutableElement>,
+ generateGetterForFields: Boolean,
+ generateSetterForFields: Boolean,
+ allFields: Boolean
+ ): EnumSet<GetterSetter> {
+ if (allFields && !(generateSetterForFields || generateGetterForFields))
+ return addAutoGeneratedAccessors(allMethods = allMethods.map { it.name }.toSet(), field)
+
+ val result = EnumSet.noneOf(GetterSetter::class.java)
+ if (generateGetterForFields)
+ result.add(GetterSetter.GETTER)
+ if (generateSetterForFields)
+ result.add(GetterSetter.SETTER)
+ return result
+ }
+
+ private fun addAutoGeneratedAccessors(
+ allMethods: Collection<String>,
+ field: JennyElement
+ ): EnumSet<GetterSetter> {
+ val result = EnumSet.noneOf(GetterSetter::class.java)
+ val camelCaseName = field.name.toCamelCase()
+ val type = field.type.toJniReturnTypeString()
+ if (shouldAddGetter(allMethods, camelCaseName, type))
+ result.add(GetterSetter.GETTER)
+ if (shouldAddSetter(allMethods, camelCaseName))
+ result.add(GetterSetter.SETTER)
+ return result
+ }
+
+ private fun shouldAddGetter(
+ allMethods: Collection<String>,
+ camelCaseName: String,
+ type: String
+ ): Boolean {
+ return !allMethods.contains("get$camelCaseName") &&
+ !(type == "jboolean" && allMethods.contains("is$camelCaseName"))
+ }
+
+ private fun shouldAddSetter(allMethods: Collection<String>, camelCaseName: String): Boolean {
+ return !allMethods.contains("set$camelCaseName")
+ }
+
+ enum class GetterSetter {
+ GETTER, SETTER
+ }
+}
diff --git a/compiler/src/main/java/io/github/landerlyoung/jenny/utils/FileHandler.kt b/compiler/src/main/java/io/github/landerlyoung/jenny/utils/FileHandler.kt
new file mode 100644
index 0000000..3aa1696
--- /dev/null
+++ b/compiler/src/main/java/io/github/landerlyoung/jenny/utils/FileHandler.kt
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2025 The Qt Company Ltd.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.github.landerlyoung.jenny.utils
+
+import io.github.landerlyoung.jenny.exception.FileExceptionHandler
+import io.github.landerlyoung.jenny.exception.JennyFileException
+
+import java.io.BufferedWriter
+import java.io.File
+import java.io.FileWriter
+import java.io.OutputStream
+
+internal class FileHandler(private val file: File) {
+ private val exceptionHandler = FileExceptionHandler(file)
+
+ constructor(filePath: String) : this(File(filePath))
+ constructor(packageName: String, name: String) : this(
+ File(packageName.replace('.', File.separatorChar), name)
+ )
+
+ fun createFile(): Boolean = try {
+ if (!file.exists()) {
+ file.apply { parentFile?.mkdirs() }.createNewFile()
+ } else false
+ } catch (e: Exception) {
+ throw exceptionHandler.handle(e)
+ }
+
+ private fun writeToFile(data: String, append: Boolean = false) {
+ try {
+ if (!file.exists()) throw JennyFileException.FileNotFound(file.path)
+ BufferedWriter(FileWriter(file, append)).use { writer ->
+ writer.write(data)
+ writer.newLine()
+ }
+ } catch (e: Exception) {
+ throw exceptionHandler.handle(e)
+ }
+ }
+
+ fun appendToFile(data: String) = writeToFile(data, append = true)
+
+ fun readFromFile(): String = try {
+ if (!file.exists()) throw JennyFileException.FileNotFound(file.path)
+ file.readText()
+ } catch (e: Exception) {
+ throw exceptionHandler.handle(e)
+ }
+
+ fun readLines(): List<String> = try {
+ if (!file.exists()) throw JennyFileException.FileNotFound(file.path)
+ file.readLines()
+ } catch (e: Exception) {
+ throw exceptionHandler.handle(e)
+ }
+
+ fun deleteFile(): Boolean = try {
+ file.delete()
+ } catch (e: Exception) {
+ throw exceptionHandler.handle(e)
+ }
+
+ fun exists(): Boolean = file.exists()
+
+ fun size() = file.length()
+
+ fun lastModified() = file.lastModified()
+
+ companion object {
+ fun createOutputStreamFrom(parent: String, name: String, overwrite: Boolean = false): OutputStream {
+ val handler = FileExceptionHandler(File(parent, name))
+ return try {
+ createOutputFile(parent, name, overwrite).outputStream().buffered()
+ } catch (e: Exception) {
+ throw handler.handle(e)
+ }
+ }
+
+ fun createOutputFile(parent: String, name: String, overwrite: Boolean = false): File {
+ val file = File(parent, name)
+ val handler = FileExceptionHandler(file)
+ return try {
+ file.parentFile.mkdirs()
+ if (file.exists() && overwrite) {
+ file.delete()
+ }
+ if (!file.exists()) {
+ file.createNewFile()
+ }
+ file
+ } catch (e: Exception) {
+ throw handler.handle(e)
+ }
+ }
+ }
+}
diff --git a/compiler/src/main/java/io/github/landerlyoung/jenny/utils/JennyNameProvider.kt b/compiler/src/main/java/io/github/landerlyoung/jenny/utils/JennyNameProvider.kt
new file mode 100644
index 0000000..836caf0
--- /dev/null
+++ b/compiler/src/main/java/io/github/landerlyoung/jenny/utils/JennyNameProvider.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2025 The Qt Company Ltd.
+ * Copyright 2016 landerlyoung@gmail.com
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.github.landerlyoung.jenny.utils
+
+import io.github.landerlyoung.jenny.element.JennyElement
+import io.github.landerlyoung.jenny.element.method.JennyExecutableElement
+
+object JennyNameProvider {
+
+ fun getNativeMethodName(jniClassName: String, method: JennyExecutableElement): String {
+ return "Java_" + jniClassName + "_" + method.name.replace("_", "_1").stripNonASCII()
+ }
+
+ fun getElementName(jennyElement: JennyElement, index: Int): String {
+ val type = getType(jennyElement)
+ return "s${type}_" + jennyElement.name + "_" + index
+ }
+
+ private fun getType(jennyElement: JennyElement): String {
+ return if (jennyElement is JennyExecutableElement) "Method" else "Field"
+ }
+
+ fun getClassState(what: String = getClazz()) = "getClassInitState().$what"
+ fun getClazz() = "sClazz"
+ fun getConstructorName(index: Int) = "sConstruct_$index"
+}
diff --git a/compiler/src/main/java/io/github/landerlyoung/jenny/utils/NamespaceHelper.kt b/compiler/src/main/java/io/github/landerlyoung/jenny/utils/NamespaceHelper.kt
new file mode 100644
index 0000000..813bafc
--- /dev/null
+++ b/compiler/src/main/java/io/github/landerlyoung/jenny/utils/NamespaceHelper.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2025 The Qt Company Ltd.
+ * Copyright 2016 landerlyoung@gmail.com
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.github.landerlyoung.jenny.utils
+
+class NamespaceHelper(private var globalNamespace: String = "") {
+ private val globalNamespaces: List<String>
+ get() = globalNamespace.split("::").mapNotNull { it.trim().takeIf(String::isNotEmpty) }
+
+ private var classNamespace: String = ""
+
+ private val effectiveNamespace: String
+ get() = if (globalNamespace.isNotEmpty()) globalNamespace else classNamespace
+
+ val fileNamePrefix: String
+ get() = effectiveNamespace.split("::").joinToString("_").takeIf { it.isNotEmpty() }?.plus("_") ?: ""
+
+ val namespaceNotation: String
+ get() = effectiveNamespace
+
+ fun beginNamespace(): String = effectiveNamespace.split("::").joinToString(" ") { "namespace $it {" }
+
+ fun endNamespace(): String = effectiveNamespace.split("::").takeIf { it.isNotEmpty() }
+ ?.joinToString(" ", postfix = " // End of namespace $effectiveNamespace") { "}" }
+ ?: ""
+
+ fun assignGlobalNamespace(namespace: String) {
+ globalNamespace = namespace
+ }
+
+ fun setClassName(className: String) {
+ classNamespace = extractNamespaceFromClassName(className)
+ }
+
+ private fun extractNamespaceFromClassName(className: String): String {
+ val packageParts = className.split(".").dropLast(1)
+ return packageParts.joinToString("::")
+ }
+}
diff --git a/compiler/src/main/java/io/github/landerlyoung/jenny/utils/ParametersProvider.kt b/compiler/src/main/java/io/github/landerlyoung/jenny/utils/ParametersProvider.kt
new file mode 100644
index 0000000..ea7207d
--- /dev/null
+++ b/compiler/src/main/java/io/github/landerlyoung/jenny/utils/ParametersProvider.kt
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2025 The Qt Company Ltd.
+ * Copyright 2016 landerlyoung@gmail.com
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.github.landerlyoung.jenny.utils
+
+import io.github.landerlyoung.jenny.element.JennyElement
+import io.github.landerlyoung.jenny.element.clazz.JennyClazzElement
+import io.github.landerlyoung.jenny.element.field.JennyVarElement
+import io.github.landerlyoung.jenny.element.method.JennyExecutableElement
+import io.github.landerlyoung.jenny.element.model.JennyParameter
+
+class ParametersProvider {
+
+ fun makeParameter(
+ element: JennyElement,
+ useJniHelper: Boolean,
+ ): String = buildString {
+ if (useJniHelper) {
+ append("JNIEnv* env")
+ val isConstructor = if (element is JennyExecutableElement) element.isConstructor() else false
+ if (!isConstructor) {
+ if (element.isStatic()) {
+ append(", jclass clazz")
+ } else {
+ append(", jobject thiz")
+ }
+ }
+ }
+ }
+
+ fun getJennyElementJniParams(
+ element: JennyElement,
+ useJniHelper: Boolean,
+ isByPass:Boolean = false
+ ): String = buildString {
+ val params = mutableListOf<String>()
+
+ val baseParams = makeParameter(element, useJniHelper)
+ if (baseParams.isNotEmpty()) {
+ params.add(baseParams)
+ }
+
+ element.declaringClass?.let {
+ val clazzElement = it as? JennyClazzElement
+ if (clazzElement?.isNestedClass == true) {
+ params.add("${clazzElement.type.toJniReturnTypeString()} enclosingClass")
+ }
+ }
+
+ if (element is JennyExecutableElement) {
+ val execParams = getJniParameters(element.parameters, useJniHelper, isByPass)
+ if (execParams.isNotEmpty()) {
+ params.add(execParams)
+ }
+ }
+
+ append(params.joinToString(", "))
+ }
+
+ private fun getJniParameters(parameters: List<JennyParameter>, useJniHelper: Boolean ,isByPass:Boolean): String =
+ buildString {
+ parameters.forEach { param ->
+ if (isNotEmpty()) append(", ")
+ append(param.type.toJniTypeString(useJniHelper,isByPass))
+ append(' ')
+ append(param.name)
+ }
+ }
+
+
+ fun getJavaMethodParameters(method: JennyExecutableElement): String {
+ return method.parameters
+ .joinToString(", ") { param ->
+ "${param.type.typeName} ${param.name}"
+ }
+ }
+
+ fun getJniMethodParamVal(
+ method: JennyExecutableElement,
+ useJniHelper: Boolean = false,
+ ): String = buildString {
+ method.declaringClass?.let {
+ if ((it as JennyClazzElement).isNestedClass) {
+ append(", ")
+ append("enclosingClass")
+ if (useJniHelper)
+ append(".get()")
+ }
+ }
+
+ method.parameters.forEach { param ->
+ append(", ")
+ append(param.name)
+ if (useJniHelper && param.type.needWrapLocalRef()) {
+ append(".get()")
+ }
+ }
+ }
+
+ fun getConstexprStatement(property: JennyVarElement): String {
+ val constValue = property.call()
+
+ val jniType = property.type.toJniReturnTypeString()
+ val nativeType = if (jniType == "jstring") "auto" else jniType
+
+ val value = when (constValue) {
+ is Boolean -> if (constValue) "JNI_TRUE" else "JNI_FALSE"
+ is Number -> constValue.toString()
+ is Char -> "'${constValue}'"
+ is String -> "u8\"$constValue\""
+ else -> throw IllegalArgumentException("unknown type:$constValue " + constValue!!.javaClass)
+ }
+ return "static constexpr $nativeType ${property.name} = $value;\n"
+ }
+
+ fun returnTypeNeedCast(jniReturnType: String): Boolean {
+ return when (jniReturnType) {
+ "jclass", "jstring", "jarray", "jobjectArray",
+ "jbooleanArray", "jbyteArray", "jcharArray",
+ "jshortArray", "jintArray", "jlongArray",
+ "jfloatArray", "jdoubleArray",
+ "jthrowable", "jweak" -> true
+
+ else -> false
+ }
+ }
+}
diff --git a/compiler/src/main/java/io/github/landerlyoung/jenny/utils/Signature.kt b/compiler/src/main/java/io/github/landerlyoung/jenny/utils/Signature.kt
new file mode 100644
index 0000000..ebd17db
--- /dev/null
+++ b/compiler/src/main/java/io/github/landerlyoung/jenny/utils/Signature.kt
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2025 The Qt Company Ltd.
+ * Copyright 2016 landerlyoung@gmail.com
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.github.landerlyoung.jenny.utils
+
+import io.github.landerlyoung.jenny.element.JennyElement
+import io.github.landerlyoung.jenny.element.clazz.JennyClazzElement
+import io.github.landerlyoung.jenny.element.method.JennyExecutableElement
+import io.github.landerlyoung.jenny.element.model.type.JennyKind
+import io.github.landerlyoung.jenny.element.model.type.JennyType
+
+class Signature(private val jennyElement: JennyElement) {
+
+ private fun getSignatureClassName(type: JennyType): String {
+ val output = StringBuilder()
+ var jennyType = type
+
+ while (jennyType.isArray()) {
+ output.append('[')
+ jennyType = (jennyType.componentType ?: error("Array type missing"))
+ }
+
+ when (jennyType.jennyKind) {
+ JennyKind.BOOLEAN -> output.append('Z')
+ JennyKind.BYTE -> output.append('B')
+ JennyKind.CHAR -> output.append('C')
+ JennyKind.SHORT -> output.append('S')
+ JennyKind.INT -> output.append('I')
+ JennyKind.LONG -> output.append('J')
+ JennyKind.FLOAT -> output.append('F')
+ JennyKind.DOUBLE -> output.append('D')
+ JennyKind.VOID -> output.append('V')
+ else -> {
+ output.append(
+ 'L' + jennyType.getNonGenericType().typeName.toJniClassName(jennyType.isNestedType())
+ .substringBefore("<")
+ .replace('.', '/')
+ ).append(';')
+ }
+ }
+ return output.toString()
+ }
+
+ override fun toString(): String = buildString {
+ if (jennyElement is JennyExecutableElement) {
+ append('(')
+
+ if (jennyElement.name.contentEquals("<init>")) {
+ val clazz = jennyElement.declaringClass
+ clazz?.declaringClass?.let {
+ if ((it as JennyClazzElement).isNestedClass) {
+ append(getSignatureClassName(clazz.type))
+ }
+ }
+ }
+
+ for (param in jennyElement.parameters) {
+ append(getSignatureClassName(param.type))
+ }
+ append(')')
+ append(getSignatureClassName(jennyElement.returnType))
+ } else {
+ append(getSignatureClassName(jennyElement.type))
+ }
+ }
+
+ companion object {
+ fun getBinaryJennyElementSignature(element: JennyElement): String =
+ Signature(element).toString()
+ }
+}
diff --git a/compiler/src/main/java/io/github/landerlyoung/jenny/utils/ext.kt b/compiler/src/main/java/io/github/landerlyoung/jenny/utils/ext.kt
new file mode 100644
index 0000000..6945adc
--- /dev/null
+++ b/compiler/src/main/java/io/github/landerlyoung/jenny/utils/ext.kt
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2025 The Qt Company Ltd.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.github.landerlyoung.jenny.utils
+
+import io.github.landerlyoung.jenny.element.JennyElement
+import io.github.landerlyoung.jenny.element.model.JennyModifier
+import io.github.landerlyoung.jenny.element.model.type.JennyKind
+import io.github.landerlyoung.jenny.element.model.type.JennyType
+
+import java.util.Locale
+
+fun JennyType.toJniReturnTypeString(): String {
+ val locale = Locale.US
+ return when {
+ isPrimitive() -> "j${typeName.lowercase(locale)}"
+ isArray() -> {
+ if (componentType?.isPrimitive() == true) {
+ "j${componentType!!.typeName.lowercase(locale)}Array"
+ } else {
+ "jobjectArray"
+ }
+ }
+
+ jennyKind == JennyKind.VOID -> "void"
+ jennyKind == JennyKind.DECLARED -> {
+ when (typeName) {
+ "java.lang.String" -> "jstring"
+ "java.lang.Class" -> "jclass"
+ "java.lang.Throwable" -> "jthrowable"
+ else -> "jobject"
+ }
+ }
+
+ else -> "jobject"
+ }
+}
+
+fun JennyType.toJniTypeString(useJniHelper: Boolean, byPass: Boolean): String {
+ val jniType = toJniReturnTypeString()
+ return if (useJniHelper && this.needWrapLocalRef() && !byPass) {
+ "const ::jenny::LocalRef<$jniType>&"
+ } else {
+ jniType
+ }
+}
+
+fun JennyType.toJniCall(): String {
+ val result = if (this.isPrimitive() || this.jennyKind == JennyKind.VOID) {
+ this.typeName.lowercase()
+ } else {
+ "object"
+ }
+ return result.replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() }
+}
+
+fun Collection<JennyModifier>.print(): String {
+ return this.sorted()
+ .joinToString(" ") { it.toString().lowercase(Locale.US) }
+}
+
+internal fun String.stripNonASCII(): String = this.replace("[^a-zA-Z0-9_]".toRegex()) {
+ String.format(Locale.US, "_%05x", it.value.codePointAt(0))
+}
+
+fun JennyType.needWrapLocalRef(): Boolean {
+ return (!isPrimitive() && jennyKind != JennyKind.VOID)
+}
+
+fun String.toCamelCase() =
+ replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.US) else it.toString() }
+
+internal fun String.toJniClassName(isNested: Boolean): String {
+
+ val replaced = this.replace('.', '/')
+ if (!isNested) return replaced
+
+ val lastDotIndex = this.lastIndexOf('.')
+ val dollarIndex = this.indexOf('$')
+
+ return if (dollarIndex > lastDotIndex) {
+ replaced
+ } else {
+ val outerClassEnd =
+ this.lastIndexOf(".") // Last dot separates the outer class and inner class
+ if (outerClassEnd != -1) {
+ replaced.substring(0, outerClassEnd) + "$" + replaced.substring(outerClassEnd + 1)
+ } else {
+ replaced
+ }
+ }
+}
+fun JennyElement.isStatic() = JennyModifier.STATIC in modifiers
+internal fun JennyElement.isConstant() = (isStatic() && JennyModifier.FINAL in modifiers)
+internal fun JennyElement.isCompileTimeConstant(): Boolean {
+ if (!isConstant()) return false
+ if (type.isPrimitive() || type.typeName == "java.lang.String")
+ return true
+ return false
+}
+internal fun JennyElement.isNative() = JennyModifier.NATIVE in modifiers
+internal fun JennyElement.isPublic() = JennyModifier.PUBLIC in modifiers
+
+internal fun <T> Collection<T>.visibility(onlyPublic: Boolean): Collection<T> where T : JennyElement {
+ return if (onlyPublic) {
+ this.filter { it.isPublic() }
+ } else {
+ this
+ }
+}