diff options
| author | Ahmed El Khazari <ahmed.el.khazari@qt.io> | 2025-01-31 16:35:16 +0200 |
|---|---|---|
| committer | Ville Voutilainen <ville.voutilainen@qt.io> | 2025-04-15 05:55:01 +0000 |
| commit | e0bc3797f469d32004ab1f8401cd4b51c4bce6fd (patch) | |
| tree | 19d9b0491d5ba94d355babbc89dd5223184dcc43 | |
| parent | 5e2e5625799b7f1152daf8412555677830436fdb (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>
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 + } +} |
