feat: introduce binding generator
A squash of hundreds of commits with a binding API generator. The API generator works with a hybrid model: 1. We define methods and fields in the code. 2. Mark them with API annotations. 3. For specific use cases, we use the old approach with JNI generation comments. There is also an API to generate an AST tree, which is saved to JSON. This JSON can be used for full API generation by declaring only field/method names. Currently, it is used to generate constants. By eyeballing, 95% of the API has been converted to the new approach. This is sufficient for the current moment. Only edge cases with specific API calls are left. fix: ambiguous call for ImGui::GetColorU32 build: disable checkstyle for imgui-binding module Since the module is heavily generated, checkstyle for it doesn't make sense. build: make buildSrc to use JDK 11 build: make generateApi optional when compiling project Initially generated module was not included to the project VCS. Since it's now in the separate src folder, we don't need to call generator every time. Only when a new generation result is required.
This commit is contained in:
1 parent
e64df5a433
commit
8a8cd22ed6
323 files changed
+113518
-15318
No files matched your search
@@ -0,0 +1,155 @@
|
||||
package tool.generator.api
|
||||
|
||||
import spoon.reflect.code.CtJavaDoc
|
||||
import spoon.reflect.declaration.CtElement
|
||||
import spoon.reflect.declaration.CtField
|
||||
import spoon.reflect.declaration.CtMethod
|
||||
import spoon.reflect.declaration.CtType
|
||||
|
||||
class BindingSourceProcessor(
|
||||
private val type: CtType<*>
|
||||
) {
|
||||
companion object {
|
||||
private const val BLANK_SPACE = " "
|
||||
|
||||
private val CLEANUP_ANNOTATIONS_REGEX = Regex(
|
||||
"@" + CLEANUP_ANNOTATIONS_LIST.joinToString(separator = "|", prefix = "(", postfix = ")") + ".*"
|
||||
)
|
||||
|
||||
private val CLEANUP_IMPORTS_REGEX = Regex(
|
||||
"import.+" + CLEANUP_ANNOTATIONS_LIST.joinToString(separator = "|", prefix = "(", postfix = ")")
|
||||
)
|
||||
|
||||
fun isProcessable(type: CtType<*>): Boolean {
|
||||
return type.hasAnnotation(A_NAME_BINDING_SOURCE)
|
||||
}
|
||||
}
|
||||
|
||||
private val originalContent: String = type.position.file.readText()
|
||||
|
||||
fun process() {
|
||||
val contentToReplace = mutableMapOf<ReplacePos, String>()
|
||||
contentToReplace += collectBindingFields()
|
||||
contentToReplace += collectBindingMethods()
|
||||
contentToReplace += collectBindingAstEnums()
|
||||
|
||||
val contentBuilder = StringBuilder(originalContent)
|
||||
contentToReplace.keys.sortedDescending().forEach { replacePos ->
|
||||
contentBuilder.replace(replacePos.startIdx, replacePos.endIdx, contentToReplace[replacePos])
|
||||
}
|
||||
|
||||
cleanupBindingAnnotations(contentBuilder)
|
||||
|
||||
type.position.file.writeText(contentBuilder.toString())
|
||||
}
|
||||
|
||||
private fun collectBindingFields(): Map<ReplacePos, String> {
|
||||
val result = mutableMapOf<ReplacePos, String>()
|
||||
for (field in type.fields) {
|
||||
if (field.hasAnnotation(A_NAME_BINDING_FIELD)) {
|
||||
result += processBindingField(field)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private fun collectBindingMethods(): Map<ReplacePos, String> {
|
||||
val result = mutableMapOf<ReplacePos, String>()
|
||||
for (method in type.methods) {
|
||||
if (method.hasAnnotation(A_NAME_BINDING_METHOD)) {
|
||||
result += processBindingMethod(method)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private fun collectBindingAstEnums(): Map<ReplacePos, String> {
|
||||
val result = mutableMapOf<ReplacePos, String>()
|
||||
for (field in type.fields) {
|
||||
if (field.hasAnnotation(A_NAME_BINDING_AST_ENUM)) {
|
||||
result += processBindingAstEnum(field)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private fun cleanupBindingAnnotations(contentBuilder: StringBuilder) {
|
||||
val linesToRemove = mutableListOf<String>()
|
||||
contentBuilder.lineSequence().forEach { line ->
|
||||
if (line.contains(CLEANUP_IMPORTS_REGEX)) {
|
||||
linesToRemove += line
|
||||
}
|
||||
if (line.contains(CLEANUP_ANNOTATIONS_REGEX)) {
|
||||
linesToRemove += line
|
||||
}
|
||||
}
|
||||
linesToRemove.reversed().forEach { line ->
|
||||
val idx = contentBuilder.indexOf(line)
|
||||
contentBuilder.delete(idx, idx + line.length + 1)
|
||||
}
|
||||
}
|
||||
|
||||
private fun processBindingMethod(method: CtMethod<*>): Map<ReplacePos, String> {
|
||||
val contentToReplace = mutableMapOf<ReplacePos, String>()
|
||||
|
||||
// Remove the original javadoc comment from the generated content.
|
||||
method.comments.find { it is CtJavaDoc }?.let {
|
||||
// sourceEnd has an additional offset of 6.
|
||||
// 1 - '/'
|
||||
// 2 - '\n'
|
||||
// 3-6 - ' '
|
||||
contentToReplace[ReplacePos(it.position.sourceStart, it.position.sourceEnd + 6)] = ""
|
||||
}
|
||||
|
||||
val content = jvmMethodContent(method) + jniMethodContent(method)
|
||||
contentToReplace[method.findReplacePos()] = content.joinWithIndention()
|
||||
return contentToReplace
|
||||
}
|
||||
|
||||
private fun processBindingField(field: CtField<*>): Map<ReplacePos, String> {
|
||||
val contentToReplace = mutableMapOf<ReplacePos, String>()
|
||||
|
||||
// Remove the original javadoc comment from the generated content.
|
||||
field.comments.find { it is CtJavaDoc }?.let {
|
||||
// sourceEnd has an additional offset of 6.
|
||||
// 1 - '/'
|
||||
// 2 - '\n'
|
||||
// 3-6 - ' '
|
||||
contentToReplace[ReplacePos(it.position.sourceStart, it.position.sourceEnd + 6)] = ""
|
||||
}
|
||||
|
||||
val content = jvmFieldContent(field) + jniFieldContent(field)
|
||||
contentToReplace[field.findReplacePos()] = content.joinWithIndention()
|
||||
return contentToReplace
|
||||
}
|
||||
|
||||
private fun processBindingAstEnum(field: CtField<*>): Map<ReplacePos, String> {
|
||||
val content = astEnumContent(field.getAnnotation(A_NAME_BINDING_AST_ENUM)!!)
|
||||
val contentStr = content.joinWithIndention()
|
||||
return mapOf(field.findReplacePos() to contentStr)
|
||||
}
|
||||
|
||||
private fun List<String>.joinWithIndention(): String {
|
||||
return joinToString("\n\n") { str ->
|
||||
str.lines().joinToString("\n") {
|
||||
it.takeIf(String::isNotBlank)?.prependIndent(BLANK_SPACE) ?: it
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun CtElement.findReplacePos(): ReplacePos {
|
||||
var startIdx = annotations[0].position.sourceStart
|
||||
while (originalContent[startIdx] != '\n') {
|
||||
startIdx--
|
||||
}
|
||||
startIdx++
|
||||
val endIdx = position.sourceEnd + 1
|
||||
return ReplacePos(startIdx, endIdx)
|
||||
}
|
||||
|
||||
private data class ReplacePos(val startIdx: Int, val endIdx: Int) : Comparable<ReplacePos> {
|
||||
override fun compareTo(other: ReplacePos): Int {
|
||||
return startIdx.compareTo(other.startIdx)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package tool.generator.api
|
||||
|
||||
import org.apache.commons.io.FileUtils
|
||||
import spoon.reflect.declaration.CtType
|
||||
|
||||
class ExcludedSourceProcessor(
|
||||
private val type: CtType<*>
|
||||
) {
|
||||
companion object {
|
||||
fun isProcessable(type: CtType<*>): Boolean {
|
||||
return type.hasAnnotation(A_NAME_EXCLUDED_SOURCE)
|
||||
}
|
||||
}
|
||||
|
||||
fun process() {
|
||||
val sourceFile = type.position.file
|
||||
val parentDir = sourceFile.parentFile
|
||||
sourceFile.delete()
|
||||
if (FileUtils.isEmptyDirectory(parentDir)) {
|
||||
parentDir.delete()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package tool.generator.api
|
||||
|
||||
import spoon.reflect.declaration.CtAnnotation
|
||||
import spoon.reflect.declaration.ModifierKind
|
||||
import tool.generator.ast.AstEnumConstantDecl
|
||||
import tool.generator.ast.AstParser
|
||||
|
||||
fun astEnumContent(markerAnnotation: CtAnnotation<*>): List<String> {
|
||||
val result = mutableListOf<String>()
|
||||
|
||||
val file = markerAnnotation.getValueAsString(A_VALUE_FILE)!!
|
||||
val qualType = markerAnnotation.getValueAsString(A_VALUE_QUAL_TYPE)!!
|
||||
val sanitizeName = markerAnnotation.getValueAsString(A_VALUE_SANITIZE_NAME)?.takeIf(String::isNotEmpty) ?: qualType
|
||||
val astRoot = AstParser.readFromResource(file)
|
||||
|
||||
val enums = astRoot.decls.filterDecls0 {
|
||||
it is AstEnumConstantDecl && it.qualType == qualType
|
||||
}.map { it as AstEnumConstantDecl }
|
||||
|
||||
val factory = markerAnnotation.factory
|
||||
|
||||
enums.forEach { e ->
|
||||
val f = factory.createField<Nothing>()
|
||||
|
||||
f.setModifiers<Nothing>(setOf(ModifierKind.PUBLIC, ModifierKind.STATIC, ModifierKind.FINAL))
|
||||
f.setType<Nothing>(factory.createTypeParam("int"))
|
||||
f.setSimpleName<Nothing>(e.name.replace(sanitizeName, ""))
|
||||
buildString {
|
||||
if (e.docComment != null) {
|
||||
appendLine(e.docComment)
|
||||
if (e.value != null) {
|
||||
appendLine()
|
||||
append("<p>")
|
||||
}
|
||||
}
|
||||
if (e.value != null) {
|
||||
append("Definition: {@code ${e.value}}")
|
||||
}
|
||||
}.let {
|
||||
if (it.isNotEmpty()) {
|
||||
f.setDocComment<Nothing>(sanitizeDocComment(it))
|
||||
}
|
||||
}
|
||||
f.setAssignment<Nothing>(factory.createCodeSnippetExpression((e.evaluatedValue ?: e.order).toString()))
|
||||
|
||||
result += f.prettyprint()
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
private fun sanitizeDocComment(doc: String): String {
|
||||
return doc
|
||||
.replace("*/", "* /")
|
||||
.replace(" > ", "{@code >}")
|
||||
.replace("< <", "{@code <<}")
|
||||
.replace(" < ", "{@code <}")
|
||||
.replace("->", "{@code ->}")
|
||||
.replace("&", "{@code &}")
|
||||
}
|
||||
@@ -0,0 +1,688 @@
|
||||
package tool.generator.api
|
||||
|
||||
import spoon.reflect.code.CtStatement
|
||||
import spoon.reflect.declaration.CtField
|
||||
import spoon.reflect.declaration.CtMethod
|
||||
import spoon.reflect.declaration.CtParameter
|
||||
import spoon.reflect.declaration.ModifierKind
|
||||
import spoon.reflect.factory.Factory
|
||||
import spoon.reflect.reference.CtTypeReference
|
||||
|
||||
private fun convertParams2jni(f: Factory, params: List<CtParameter<*>>, defaults: IntArray): List<CtParameter<*>> {
|
||||
val result = mutableListOf<CtParameter<*>>()
|
||||
for ((index, p) in params.withIndex()) {
|
||||
if (defaults.isNotEmpty() && !defaults.contains(index)) {
|
||||
continue
|
||||
}
|
||||
if (p.isType("Void")) {
|
||||
continue
|
||||
}
|
||||
if (p.isType("ImVec2") || p.isType("ImVec4")) { // vec param always accepted as primitive values
|
||||
result += f.createParameter<Nothing>().apply {
|
||||
setType<Nothing>(f.createTypeParam("float"))
|
||||
setSimpleName<Nothing>("${p.simpleName}X")
|
||||
}
|
||||
result += f.createParameter<Nothing>().apply {
|
||||
setType<Nothing>(f.createTypeParam("float"))
|
||||
setSimpleName<Nothing>("${p.simpleName}Y")
|
||||
}
|
||||
if (p.isType("ImVec4")) {
|
||||
result += f.createParameter<Nothing>().apply {
|
||||
setType<Nothing>(f.createTypeParam("float"))
|
||||
setSimpleName<Nothing>("${p.simpleName}Z")
|
||||
}
|
||||
result += f.createParameter<Nothing>().apply {
|
||||
setType<Nothing>(f.createTypeParam("float"))
|
||||
setSimpleName<Nothing>("${p.simpleName}W")
|
||||
}
|
||||
}
|
||||
} else if (p.isType("ImRect")) {
|
||||
result += f.createParameter<Nothing>().apply {
|
||||
setType<Nothing>(f.createTypeParam("float"))
|
||||
setSimpleName<Nothing>("${p.simpleName}MinX")
|
||||
}
|
||||
result += f.createParameter<Nothing>().apply {
|
||||
setType<Nothing>(f.createTypeParam("float"))
|
||||
setSimpleName<Nothing>("${p.simpleName}MinY")
|
||||
}
|
||||
result += f.createParameter<Nothing>().apply {
|
||||
setType<Nothing>(f.createTypeParam("float"))
|
||||
setSimpleName<Nothing>("${p.simpleName}MaxX")
|
||||
}
|
||||
result += f.createParameter<Nothing>().apply {
|
||||
setType<Nothing>(f.createTypeParam("float"))
|
||||
setSimpleName<Nothing>("${p.simpleName}MaxY")
|
||||
}
|
||||
} else if (p.isType("ImPlotPoint")) {
|
||||
result += f.createParameter<Nothing>().apply {
|
||||
setType<Nothing>(f.createTypeParam("double"))
|
||||
setSimpleName<Nothing>("${p.simpleName}X")
|
||||
}
|
||||
result += f.createParameter<Nothing>().apply {
|
||||
setType<Nothing>(f.createTypeParam("double"))
|
||||
setSimpleName<Nothing>("${p.simpleName}Y")
|
||||
}
|
||||
} else if (p.isType("ImPlotRange")) {
|
||||
result += f.createParameter<Nothing>().apply {
|
||||
setType<Nothing>(f.createTypeParam("double"))
|
||||
setSimpleName<Nothing>("${p.simpleName}Min")
|
||||
}
|
||||
result += f.createParameter<Nothing>().apply {
|
||||
setType<Nothing>(f.createTypeParam("double"))
|
||||
setSimpleName<Nothing>("${p.simpleName}Max")
|
||||
}
|
||||
} else if (p.isType("ImPlotLimits")) {
|
||||
result += f.createParameter<Nothing>().apply {
|
||||
setType<Nothing>(f.createTypeParam("double"))
|
||||
setSimpleName<Nothing>("${p.simpleName}MinX")
|
||||
}
|
||||
result += f.createParameter<Nothing>().apply {
|
||||
setType<Nothing>(f.createTypeParam("double"))
|
||||
setSimpleName<Nothing>("${p.simpleName}MinY")
|
||||
}
|
||||
result += f.createParameter<Nothing>().apply {
|
||||
setType<Nothing>(f.createTypeParam("double"))
|
||||
setSimpleName<Nothing>("${p.simpleName}MaxX")
|
||||
}
|
||||
result += f.createParameter<Nothing>().apply {
|
||||
setType<Nothing>(f.createTypeParam("double"))
|
||||
setSimpleName<Nothing>("${p.simpleName}MaxY")
|
||||
}
|
||||
} else if (p.isType("TextEditorCoordinates")) {
|
||||
result += f.createParameter<Nothing>().apply {
|
||||
setType<Nothing>(f.createTypeParam("int"))
|
||||
setSimpleName<Nothing>("${p.simpleName}Line")
|
||||
}
|
||||
result += f.createParameter<Nothing>().apply {
|
||||
setType<Nothing>(f.createTypeParam("int"))
|
||||
setSimpleName<Nothing>("${p.simpleName}Column")
|
||||
}
|
||||
} else if (p.isType("String[]")) {
|
||||
result += f.createParameter<Nothing>().apply {
|
||||
setType<Nothing>(f.createTypeParam("String[]"))
|
||||
setSimpleName<Nothing>(p.simpleName)
|
||||
}
|
||||
result += f.createParameter<Nothing>().apply {
|
||||
setType<Nothing>(f.createTypeParam("int"))
|
||||
setSimpleName<Nothing>("${p.simpleName}$PARAM_ARR_LEN_POSTFIX")
|
||||
}
|
||||
} else {
|
||||
result += f.createParameter<Nothing>().apply {
|
||||
val type = if (p.type.isPtrClass()) {
|
||||
f.createTypeParam("long")
|
||||
} else if (p.type.isClass) {
|
||||
when (p.type.simpleName) {
|
||||
"ImBoolean" -> f.createTypeParam("boolean[]")
|
||||
"ImInt" -> f.createTypeParam("int[]")
|
||||
"ImFloat" -> f.createTypeParam("float[]")
|
||||
"ImLong" -> f.createTypeParam("long[]")
|
||||
"ImDouble" -> f.createTypeParam("double[]")
|
||||
else -> p.type
|
||||
}
|
||||
} else {
|
||||
p.type
|
||||
}
|
||||
|
||||
setType<Nothing>(type)
|
||||
setSimpleName<Nothing>(p.simpleName)
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private fun joinInBodyParams(params: List<CtParameter<*>>, defaults: IntArray): String {
|
||||
fun param2str(p: CtParameter<*>): String {
|
||||
return if (p.type.isPtrClass()) {
|
||||
"reinterpret_cast<${p.type.simpleName}*>(${p.simpleName})"
|
||||
} else if (p.isPrimitivePtrType()) {
|
||||
"&${p.simpleName}[0]"
|
||||
} else if (p.type.isClass) {
|
||||
when (p.type.simpleName) {
|
||||
"ImBoolean", "ImInt", "ImFloat", "ImLong", "ImDouble" -> {
|
||||
"(${p.simpleName} != NULL ? &${p.simpleName}[0] : NULL)"
|
||||
}
|
||||
|
||||
"ImRect" -> {
|
||||
"ImRect(${p.simpleName}MinX, ${p.simpleName}MinY, ${p.simpleName}MaxX, ${p.simpleName}MaxY)"
|
||||
}
|
||||
|
||||
"ImPlotPoint" -> {
|
||||
"ImPlotPoint(${p.simpleName}X, ${p.simpleName}Y)"
|
||||
}
|
||||
|
||||
"ImPlotRange" -> {
|
||||
"ImPlotRange(${p.simpleName}Min, ${p.simpleName}Max)"
|
||||
}
|
||||
|
||||
"ImPlotLimits" -> {
|
||||
"ImPlotLimits(${p.simpleName}MinX, ${p.simpleName}MinY, ${p.simpleName}MaxX, ${p.simpleName}MaxY)"
|
||||
}
|
||||
|
||||
"TextEditorCoordinates" -> {
|
||||
"TextEditor::Coordinates(${p.simpleName}Line, ${p.simpleName}Column)"
|
||||
}
|
||||
|
||||
else -> p.simpleName
|
||||
}
|
||||
} else {
|
||||
p.simpleName
|
||||
}
|
||||
}
|
||||
|
||||
val visibleParams = mutableListOf<String>()
|
||||
for ((index, p) in params.withIndex()) {
|
||||
if (defaults.isNotEmpty() && !defaults.contains(index)) {
|
||||
visibleParams += p.getAnnotation(A_NAME_OPT_ARG)!!.getValueAsString(A_VALUE_CALL_VALUE)
|
||||
continue
|
||||
}
|
||||
if (p.isType("Void")) {
|
||||
visibleParams += p.getAnnotation(A_NAME_ARG_VALUE)?.getValueAsString(A_VALUE_CALL_VALUE) ?: p.simpleName
|
||||
continue
|
||||
}
|
||||
var value = param2str(p)
|
||||
p.getAnnotation(A_NAME_ARG_VALUE)?.let { a ->
|
||||
a.getValueAsString(A_VALUE_CALL_VALUE).takeIf(String::isNotEmpty)?.let { v ->
|
||||
value = v
|
||||
}
|
||||
a.getValueAsString(A_VALUE_CALL_PREFIX).takeIf(String::isNotEmpty)?.let { v ->
|
||||
value = v + value
|
||||
}
|
||||
a.getValueAsString(A_VALUE_CALL_SUFFIX).takeIf(String::isNotEmpty)?.let { v ->
|
||||
value += v
|
||||
}
|
||||
a.getValueAsString(A_VALUE_STATIC_CAST).takeIf(String::isNotEmpty)?.let { v ->
|
||||
value = "static_cast<$v>($value)"
|
||||
}
|
||||
a.getValueAsString(A_VALUE_REINTERPRET_CAST).takeIf(String::isNotEmpty)?.let { v ->
|
||||
value = "reinterpret_cast<$v>($value)"
|
||||
}
|
||||
}
|
||||
visibleParams += value
|
||||
}
|
||||
return visibleParams.joinToString()
|
||||
}
|
||||
|
||||
private fun createMethod(mOrig: CtMethod<*>, params: List<CtParameter<*>>, defaults: IntArray): CtMethod<*> {
|
||||
val f = mOrig.factory
|
||||
val newM = f.createMethod<Nothing>()
|
||||
|
||||
newM.setParent<Nothing>(mOrig.parent)
|
||||
|
||||
newM.addModifier<Nothing>(ModifierKind.PRIVATE)
|
||||
newM.addModifier<Nothing>(ModifierKind.NATIVE)
|
||||
if (mOrig.isStatic) {
|
||||
newM.addModifier<Nothing>(ModifierKind.STATIC)
|
||||
}
|
||||
|
||||
if (DST_RETURN_TYPE_SET.contains(mOrig.type.simpleName)) {
|
||||
newM.setType<Nothing>(f.createTypeParam("void"))
|
||||
} else if (mOrig.type.isPtrClass()) { // classes returned as a pointer
|
||||
newM.setType<Nothing>(f.createTypeParam("long"))
|
||||
} else {
|
||||
newM.setType<Nothing>(mOrig.type)
|
||||
}
|
||||
|
||||
newM.setSimpleName<Nothing>(mOrig.getJniName())
|
||||
|
||||
convertParams2jni(f, params, defaults).forEach {
|
||||
newM.addParameter<Nothing>(it)
|
||||
}
|
||||
|
||||
if (DST_RETURN_TYPE_SET.contains(mOrig.type.simpleName)) {
|
||||
newM.addParameterAt<Nothing>(0, f.createParameter<Nothing>().apply {
|
||||
setType<Nothing>(mOrig.type)
|
||||
setSimpleName<Nothing>("dst")
|
||||
})
|
||||
}
|
||||
|
||||
newM.setBody<Nothing>(f.createCodeSnippet(
|
||||
buildString {
|
||||
val jniCpyReturn = DST_RETURN_TYPE_SET.contains(mOrig.type.simpleName)
|
||||
val isStrReturn = mOrig.isType("String")
|
||||
|
||||
if (jniCpyReturn) {
|
||||
append("Jni::${mOrig.type.simpleName}Cpy(env, ")
|
||||
} else {
|
||||
if (!mOrig.isType("void")) {
|
||||
append("return ")
|
||||
if (isStrReturn) {
|
||||
append("env->NewStringUTF(")
|
||||
} else if (!mOrig.type.isPrimitive) {
|
||||
append("($PTR_JNI_CAST)")
|
||||
}
|
||||
mOrig.getAnnotation(A_NAME_RETURN_VALUE)?.let { a ->
|
||||
append(a.getValueAsString(A_VALUE_CALL_PREFIX))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val callName = mOrig.getAnnotation(A_NAME_BINDING_METHOD)?.let { a ->
|
||||
a.getValueAsString(A_VALUE_CALL_NAME)?.takeIf(String::isNotEmpty)
|
||||
} ?: mOrig.simpleName
|
||||
|
||||
val callPtr = mOrig.parent.getAnnotation(A_NAME_BINDING_SOURCE)!!.let { a ->
|
||||
a.getValueAsString(A_VALUE_CALL_PTR)?.takeIf(String::isNotEmpty) ?: if (mOrig.isStatic) {
|
||||
mOrig.declaringType.simpleName
|
||||
} else {
|
||||
PTR_JNI_THIS
|
||||
}
|
||||
}
|
||||
|
||||
val callOperator = mOrig.parent.getAnnotation(A_NAME_BINDING_SOURCE)!!.let { a ->
|
||||
a.getValueAsString(A_VALUE_CALL_OPERATOR)?.takeIf(String::isNotEmpty) ?: if (mOrig.isStatic) {
|
||||
"::"
|
||||
} else {
|
||||
"->"
|
||||
}
|
||||
}
|
||||
|
||||
append("$callPtr$callOperator${callName}(")
|
||||
append(joinInBodyParams(params, defaults))
|
||||
append(')')
|
||||
|
||||
mOrig.getAnnotation(A_NAME_RETURN_VALUE)?.let { a ->
|
||||
append(a.getValueAsString(A_VALUE_CALL_SUFFIX))
|
||||
}
|
||||
|
||||
if (jniCpyReturn) {
|
||||
append(", dst)")
|
||||
} else if (isStrReturn) {
|
||||
append(')')
|
||||
}
|
||||
}
|
||||
))
|
||||
|
||||
return newM
|
||||
}
|
||||
|
||||
private fun createMethodVecValueReturn(
|
||||
vecVal: String,
|
||||
mOrig: CtMethod<*>,
|
||||
nameOrig: String,
|
||||
params: List<CtParameter<*>>,
|
||||
defaults: IntArray
|
||||
): CtMethod<*> {
|
||||
val callPtr = mOrig.parent.getAnnotation(A_NAME_BINDING_SOURCE)!!.let { a ->
|
||||
a.getValueAsString(A_VALUE_CALL_PTR)?.takeIf(String::isNotEmpty) ?: if (mOrig.isStatic) {
|
||||
mOrig.declaringType.simpleName
|
||||
} else {
|
||||
PTR_JNI_THIS
|
||||
}
|
||||
}
|
||||
|
||||
val callOperator = mOrig.parent.getAnnotation(A_NAME_BINDING_SOURCE)!!.let { a ->
|
||||
a.getValueAsString(A_VALUE_CALL_OPERATOR)?.takeIf(String::isNotEmpty) ?: if (mOrig.isStatic) {
|
||||
"::"
|
||||
} else {
|
||||
"->"
|
||||
}
|
||||
}
|
||||
|
||||
val mNew = mOrig.clone()
|
||||
mNew.setSimpleName<Nothing>(mOrig.simpleName + vecVal.capitalize())
|
||||
mNew.removeParameter(mNew.parameters[0]) // dst param which was added during orig method creation
|
||||
mNew.type.setSimpleName<Nothing>("float")
|
||||
mNew.setBody<Nothing>(mOrig.factory.createCodeSnippet(
|
||||
buildString {
|
||||
append("return ")
|
||||
append("$callPtr$callOperator$nameOrig(")
|
||||
append(joinInBodyParams(params, defaults))
|
||||
append(").$vecVal")
|
||||
}
|
||||
))
|
||||
return mNew
|
||||
}
|
||||
|
||||
private fun convertManualJni(mOrig: CtMethod<*>, method: CtMethod<*>): CtMethod<*>? {
|
||||
val mNew = method.clone()
|
||||
|
||||
val getLines = mutableListOf<String>()
|
||||
val releaseLines = mutableListOf<String>()
|
||||
|
||||
mNew.parameters.forEach { p ->
|
||||
var isConverted = false
|
||||
|
||||
if (p.isPrimitivePtrType()) {
|
||||
val typeCast = PRIMITIVE_PTR_TYPECAST_MAP[p.type.simpleName]
|
||||
getLines += "auto ${p.simpleName} = obj_${p.simpleName} == NULL ? NULL : (${typeCast})env->GetPrimitiveArrayCritical(obj_${p.simpleName}, JNI_FALSE)"
|
||||
releaseLines += "if (${p.simpleName} != NULL) env->ReleasePrimitiveArrayCritical(obj_${p.simpleName}, ${p.simpleName}, JNI_FALSE)"
|
||||
isConverted = true
|
||||
}
|
||||
|
||||
if (p.isType("String")) {
|
||||
getLines += "auto ${p.simpleName} = obj_${p.simpleName} == NULL ? NULL : (char*)env->GetStringUTFChars(obj_${p.simpleName}, JNI_FALSE)"
|
||||
releaseLines += "if (${p.simpleName} != NULL) env->ReleaseStringUTFChars(obj_${p.simpleName}, ${p.simpleName})"
|
||||
isConverted = true
|
||||
}
|
||||
|
||||
if (p.isType("String[]")) {
|
||||
val countParam = "${p.simpleName}$PARAM_ARR_LEN_POSTFIX"
|
||||
getLines += """
|
||||
const char* ${p.simpleName}[$countParam];
|
||||
for (int i = 0; i < ${p.simpleName}$PARAM_ARR_LEN_POSTFIX; i++) {
|
||||
const jstring str = (jstring)env->GetObjectArrayElement(obj_${p.simpleName}, i);
|
||||
auto rawStr = (char*)env->GetStringUTFChars(str, JNI_FALSE);
|
||||
${p.simpleName}[i] = rawStr;
|
||||
}
|
||||
""".trimIndent()
|
||||
releaseLines += """
|
||||
for (int i = 0; i < $countParam; i++) {
|
||||
const jstring str = (jstring)env->GetObjectArrayElement(obj_${p.simpleName}, i);
|
||||
env->ReleaseStringUTFChars(str, ${p.simpleName}[i]);
|
||||
}
|
||||
""".trimIndent()
|
||||
isConverted = true
|
||||
}
|
||||
|
||||
if (p.isType("ImVec2[]")) {
|
||||
val lengthVar = "${p.simpleName}Length"
|
||||
getLines += """
|
||||
int $lengthVar = env->GetArrayLength(obj_${p.simpleName});
|
||||
ImVec2 ${p.simpleName}[$lengthVar];
|
||||
for (int i = 0; i < $lengthVar; i++) {
|
||||
jobject src = env->GetObjectArrayElement(obj_${p.simpleName}, i);
|
||||
ImVec2 dst;
|
||||
Jni::ImVec2Cpy(env, src, &dst);
|
||||
${p.simpleName}[i] = dst;
|
||||
}
|
||||
""".trimIndent()
|
||||
isConverted = true
|
||||
}
|
||||
|
||||
if (p.isType("ImVec4[]")) {
|
||||
val lengthVar = "${p.simpleName}Length"
|
||||
getLines += """
|
||||
int $lengthVar = env->GetArrayLength(obj_${p.simpleName});
|
||||
ImVec4 ${p.simpleName}[$lengthVar];
|
||||
for (int i = 0; i < $lengthVar; i++) {
|
||||
jobject src = env->GetObjectArrayElement(obj_${p.simpleName}, i);
|
||||
ImVec4 dst;
|
||||
Jni::ImVec4Cpy(env, src, &dst);
|
||||
${p.simpleName}[i] = dst;
|
||||
}
|
||||
""".trimIndent()
|
||||
isConverted = true
|
||||
}
|
||||
|
||||
fun isAddPrefixType(type: CtTypeReference<*>): Boolean {
|
||||
return setOf(
|
||||
"String[]",
|
||||
"ImVec2[]",
|
||||
"ImVec4[]",
|
||||
).contains(type.simpleName)
|
||||
}
|
||||
|
||||
if (isConverted && (!mNew.isType("void") || isAddPrefixType(p.type))) {
|
||||
p.setSimpleName<Nothing>("obj_" + p.simpleName)
|
||||
}
|
||||
}
|
||||
|
||||
// Sine we work with an already transformed method, which doesn't contain original information,
|
||||
// we need to process the original method instead. For example, ImVec args are transformed into floats.
|
||||
mOrig.parameters.forEach { p ->
|
||||
// Transformed method may not have the current param. For example, if it's optional.
|
||||
// So for ImVec arguments we check, if there is a parameter with the same name and "X" attached.
|
||||
// It's a sort of marker in our case.
|
||||
if (method.parameters.find { it.simpleName == p.simpleName + "X" } != null) {
|
||||
if (p.isType("ImVec2")) {
|
||||
getLines += "ImVec2 ${p.simpleName} = ImVec2(${p.simpleName}X, ${p.simpleName}Y)"
|
||||
}
|
||||
if (p.isType("ImVec4")) {
|
||||
getLines += "ImVec4 ${p.simpleName} = ImVec4(${p.simpleName}X, ${p.simpleName}Y, ${p.simpleName}Z, ${p.simpleName}W)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (getLines.isNotEmpty() || releaseLines.isNotEmpty()) {
|
||||
val bOrigCode = mNew.body.getLastStatement<CtStatement>().prettyprint()
|
||||
|
||||
mNew.body.statements.clear()
|
||||
|
||||
getLines.forEach {
|
||||
mNew.body.addStatement<Nothing>(mNew.factory.createCodeSnippet(it))
|
||||
}
|
||||
|
||||
mNew.body.addStatement<Nothing>(mNew.factory.createCodeSnippet(bOrigCode.replace("return ", "auto _result = ")))
|
||||
|
||||
releaseLines.forEach {
|
||||
mNew.body.addStatement<Nothing>(mNew.factory.createCodeSnippet(it))
|
||||
}
|
||||
|
||||
if (!mNew.isType("void")) {
|
||||
mNew.body.addStatement<Nothing>(mNew.factory.createCodeSnippet("return _result"))
|
||||
}
|
||||
|
||||
return mNew
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
private fun CtMethod<*>.prettyprint(isManual: Boolean): String {
|
||||
return buildString {
|
||||
val str = prettyprint().let {
|
||||
if (isManual) {
|
||||
it.replaceFirst(" {", "; /*MANUAL")
|
||||
} else {
|
||||
it.replaceFirst(" {", "; /*")
|
||||
}
|
||||
}
|
||||
append(str)
|
||||
lastIndexOf("}").let { idx ->
|
||||
replace(idx, idx + 1, "*/")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun transformMethodToContent(
|
||||
mOrig: CtMethod<*>,
|
||||
params: List<CtParameter<*>> = emptyList(),
|
||||
defaults: IntArray = intArrayOf()
|
||||
): List<String> {
|
||||
fun CtMethod<*>.printJni(): String {
|
||||
return convertManualJni(mOrig, this)?.prettyprint(true) ?: prettyprint(false)
|
||||
}
|
||||
|
||||
val methods = mutableListOf<String>()
|
||||
val mNew = createMethod(mOrig, params, defaults)
|
||||
|
||||
methods += mNew.printJni()
|
||||
|
||||
if (mOrig.isType("ImVec2") || mOrig.isType("ImVec4")) {
|
||||
methods += createMethodVecValueReturn("x", mNew, mOrig.simpleName, params, defaults).printJni()
|
||||
methods += createMethodVecValueReturn("y", mNew, mOrig.simpleName, params, defaults).printJni()
|
||||
if (mOrig.isType("ImVec4")) {
|
||||
methods += createMethodVecValueReturn("z", mNew, mOrig.simpleName, params, defaults).printJni()
|
||||
methods += createMethodVecValueReturn("w", mNew, mOrig.simpleName, params, defaults).printJni()
|
||||
}
|
||||
}
|
||||
|
||||
return methods
|
||||
}
|
||||
|
||||
fun jniMethodContent(method: CtMethod<*>): List<String> {
|
||||
val methods = mutableListOf<String>()
|
||||
|
||||
for (paramsSize in (findFirstOptParam(method)..method.parameters.size)) {
|
||||
val params = method.parameters.subList(0, paramsSize)
|
||||
methods += transformMethodToContent(method, params)
|
||||
}
|
||||
|
||||
for (defaults in findDefaultsCombinations(method)) {
|
||||
methods += transformMethodToContent(method, method.parameters, defaults)
|
||||
}
|
||||
|
||||
return methods
|
||||
}
|
||||
|
||||
private fun createFieldGetContent(field: CtField<*>): List<String> {
|
||||
val f = field.factory
|
||||
|
||||
val getAccessor = f.createMethod<Nothing>()
|
||||
getAccessor.setParent<Nothing>(field.parent)
|
||||
getAccessor.setSimpleName<Nothing>("get${field.simpleName}")
|
||||
getAccessor.setType<Nothing>(field.type)
|
||||
getAccessor.addModifier<Nothing>(ModifierKind.PRIVATE)
|
||||
getAccessor.setAnnotations<Nothing>(field.annotations)
|
||||
|
||||
val result = mutableListOf<String>()
|
||||
|
||||
if (field.hasAnnotation(A_NAME_TYPE_ARRAY)) {
|
||||
val arrayType = field.getAnnotation(A_NAME_TYPE_ARRAY)!!.getValueAsString(A_VALUE_TYPE)
|
||||
val arraySize = field.getAnnotation(A_NAME_TYPE_ARRAY)!!.getValueAsString(A_VALUE_SIZE)
|
||||
|
||||
val getArray = getAccessor.clone()
|
||||
getArray.addModifier<Nothing>(ModifierKind.NATIVE)
|
||||
getArray.setSimpleName<Nothing>("nGet${field.simpleName}")
|
||||
getArray.setType<Nothing>(f.createTypeParam("$arrayType[]"))
|
||||
|
||||
when (arrayType) {
|
||||
"boolean", "short", "int", "float", "double", "long" -> {
|
||||
getArray.setBody<Nothing>(
|
||||
f.createCodeSnippet(
|
||||
"""
|
||||
j$arrayType jBuf[$arraySize];
|
||||
for (int i = 0; i < $arraySize; i++)
|
||||
jBuf[i] = $PTR_JNI_THIS->${field.getCallName()}[i];
|
||||
j${arrayType}Array result = env->New${arrayType.capitalize()}Array($arraySize);
|
||||
env->Set${arrayType.capitalize()}ArrayRegion(result, 0, $arraySize, jBuf);
|
||||
return result
|
||||
""".trimIndent()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
"ImVec2", "ImVec4" -> {
|
||||
getArray.setBody<Nothing>(
|
||||
f.createCodeSnippet(
|
||||
"""
|
||||
return Jni::New${arrayType}Array(env, $PTR_JNI_THIS->${field.getCallName()}, $arraySize)
|
||||
""".trimIndent()
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
result += getArray.prettyprint(false)
|
||||
|
||||
when (arrayType) {
|
||||
"boolean", "short", "int", "float", "double", "long" -> {
|
||||
val getArrayIdx = getArray.clone()
|
||||
getArrayIdx.addParameter<Nothing>(f.createParameter<Nothing>().apply {
|
||||
setType<Nothing>(f.createTypeParam("int"))
|
||||
setSimpleName<Nothing>("idx")
|
||||
})
|
||||
getArrayIdx.setType<Nothing>(f.createTypeParam(arrayType))
|
||||
getArrayIdx.setBody<Nothing>(f.createCodeSnippet("return $PTR_JNI_THIS->${field.getCallName()}[idx]"))
|
||||
|
||||
result += getArrayIdx.prettyprint(false)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
getAccessor.addAnnotation<Nothing>(f.createAnnotation(f.createTypeReference<Nothing?>().apply {
|
||||
setSimpleName<Nothing>("imgui.binding.annotation.BindingMethod")
|
||||
}).apply {
|
||||
addValue<Nothing>(A_VALUE_CALL_NAME, field.getCallName())
|
||||
})
|
||||
|
||||
result += transformMethodToContent(getAccessor).map {
|
||||
it.replace("$PTR_JNI_THIS->${getAccessor.simpleName}()", "$PTR_JNI_THIS->${field.getCallName()}")
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
private fun createFieldSetContent(field: CtField<*>): List<String> {
|
||||
val f = field.factory
|
||||
|
||||
val setAccessor = f.createMethod<Nothing>()
|
||||
setAccessor.setType<Nothing>(f.createTypeParam("void"))
|
||||
setAccessor.setParent<Nothing>(field.parent)
|
||||
setAccessor.setSimpleName<Nothing>("set${field.simpleName}")
|
||||
setAccessor.addModifier<Nothing>(ModifierKind.PRIVATE)
|
||||
|
||||
val valueParam = f.createParameter<Nothing>().apply {
|
||||
setType<Nothing>(field.type)
|
||||
setSimpleName<Nothing>("value")
|
||||
}
|
||||
|
||||
// Add this param to the method placeholder, since some transformations can check it.
|
||||
setAccessor.addParameter<Nothing>(valueParam)
|
||||
|
||||
val result = mutableListOf<String>()
|
||||
|
||||
if (field.hasAnnotation(A_NAME_TYPE_ARRAY)) {
|
||||
val arrayType = field.getAnnotation(A_NAME_TYPE_ARRAY)!!.getValueAsString(A_VALUE_TYPE)
|
||||
val arraySize = field.getAnnotation(A_NAME_TYPE_ARRAY)!!.getValueAsString(A_VALUE_SIZE)
|
||||
|
||||
val setArray = setAccessor.clone()
|
||||
setArray.addModifier<Nothing>(ModifierKind.NATIVE)
|
||||
setArray.setSimpleName<Nothing>("nSet${field.simpleName}")
|
||||
|
||||
when (arrayType) {
|
||||
"boolean", "short", "int", "float", "double", "long" -> {
|
||||
setArray.setBody<Nothing>(
|
||||
f.createCodeSnippet(
|
||||
"""
|
||||
for (int i = 0; i < $arraySize; i++)
|
||||
$PTR_JNI_THIS->${field.getCallName()}[i] = value[i]
|
||||
""".trimIndent()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
"ImVec2", "ImVec4" -> {
|
||||
setArray.setBody<Nothing>(
|
||||
f.createCodeSnippet("""
|
||||
Jni::${arrayType}ArrayCpy(env, value, $PTR_JNI_THIS->${field.getCallName()}, $arraySize)
|
||||
""".trimIndent()
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
result += setArray.prettyprint(false)
|
||||
|
||||
when (arrayType) {
|
||||
"boolean", "short", "int", "float", "double", "long" -> {
|
||||
val setArrayIdx = setArray.clone()
|
||||
setArrayIdx.parameters.clear()
|
||||
setArrayIdx.addParameter<Nothing>(f.createParameter<Nothing>().apply {
|
||||
setType<Nothing>(f.createTypeParam("int"))
|
||||
setSimpleName<Nothing>("idx")
|
||||
})
|
||||
setArrayIdx.addParameter<Nothing>(f.createParameter<Nothing>().apply {
|
||||
setType<Nothing>(f.createTypeParam(arrayType))
|
||||
setSimpleName<Nothing>("value")
|
||||
})
|
||||
setArrayIdx.setType<Nothing>(f.createTypeParam(arrayType))
|
||||
setArrayIdx.setBody<Nothing>(f.createCodeSnippet("$PTR_JNI_THIS->${field.getCallName()}[idx] = value"))
|
||||
|
||||
result += setArrayIdx.prettyprint(false)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
result += transformMethodToContent(setAccessor, listOf(valueParam)).map {
|
||||
it.replace(
|
||||
"$PTR_JNI_THIS->${setAccessor.simpleName}\\((.+)\\)".toRegex(),
|
||||
"$PTR_JNI_THIS->${field.getCallName()} = $1"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
fun jniFieldContent(field: CtField<*>): List<String> {
|
||||
val fields = mutableListOf<String>()
|
||||
val a = field.getAnnotation(A_NAME_BINDING_FIELD)
|
||||
if (a?.containsValue(A_VALUE_ACCESSORS, A_TYPE_VALUE_ACCESSOR_GETTER) == true) {
|
||||
fields += createFieldGetContent(field)
|
||||
}
|
||||
if (a?.containsValue(A_VALUE_ACCESSORS, A_TYPE_VALUE_ACCESSOR_SETTER) == true) {
|
||||
fields += createFieldSetContent(field)
|
||||
}
|
||||
return fields
|
||||
}
|
||||
@@ -0,0 +1,713 @@
|
||||
package tool.generator.api
|
||||
|
||||
import spoon.reflect.code.CtJavaDoc
|
||||
import spoon.reflect.code.CtJavaDocTag
|
||||
import spoon.reflect.declaration.CtField
|
||||
import spoon.reflect.declaration.CtMethod
|
||||
import spoon.reflect.declaration.CtParameter
|
||||
import spoon.reflect.declaration.ModifierKind
|
||||
import kotlin.math.absoluteValue
|
||||
|
||||
private fun createStaticStructFieldName(method: CtMethod<*>): String {
|
||||
return "_${method.simpleName.toUpperCase()}_${method.parameters.hashCode().absoluteValue}"
|
||||
}
|
||||
|
||||
private fun createStaticStructField(typeName: String, fieldName: String): String {
|
||||
return "private static final $typeName $fieldName = new $typeName(0);"
|
||||
}
|
||||
|
||||
private fun joinInBodyParams(params: List<CtParameter<*>>, defaults: IntArray): String {
|
||||
fun param2str(p: CtParameter<*>): String {
|
||||
return if (p.type.isPtrClass()) {
|
||||
"${p.simpleName}.$PTR_JVM_FIELD"
|
||||
} else if (p.type.isClass) {
|
||||
when (p.type.simpleName) {
|
||||
"ImBoolean", "ImShort", "ImInt", "ImFloat", "ImLong", "ImDouble" -> {
|
||||
"${p.simpleName} != null ? ${p.simpleName}.getData() : null"
|
||||
}
|
||||
|
||||
"ImVec2" -> "${p.simpleName}.x, ${p.simpleName}.y"
|
||||
"ImVec4" -> "${p.simpleName}.x, ${p.simpleName}.y, ${p.simpleName}.z, ${p.simpleName}.w"
|
||||
"ImRect" -> "${p.simpleName}.min.x, ${p.simpleName}.min.y, ${p.simpleName}.max.x, ${p.simpleName}.max.y"
|
||||
|
||||
"ImPlotPoint" -> "${p.simpleName}.x, ${p.simpleName}.y"
|
||||
"ImPlotRange" -> "${p.simpleName}.min, ${p.simpleName}.max"
|
||||
"ImPlotLimits" -> "${p.simpleName}.x.min, ${p.simpleName}.y.min, ${p.simpleName}.x.max, ${p.simpleName}.y.max"
|
||||
|
||||
"TextEditorCoordinates" -> "${p.simpleName}.mLine, ${p.simpleName}.mColumn"
|
||||
|
||||
"String[]" -> "${p.simpleName}, ${p.simpleName}.length"
|
||||
|
||||
"boolean[]", "byte[]", "short[]", "int[]", "float[]", "long[]", "double[]" -> p.simpleName
|
||||
|
||||
else -> p.simpleName
|
||||
}
|
||||
} else {
|
||||
p.simpleName
|
||||
}
|
||||
}
|
||||
|
||||
val visibleParams = mutableListOf<String>()
|
||||
for ((index, p) in params.withIndex()) {
|
||||
if (defaults.isNotEmpty() && !defaults.contains(index)) {
|
||||
continue
|
||||
}
|
||||
if (p.isType("Void")) {
|
||||
continue
|
||||
}
|
||||
visibleParams += param2str(p)
|
||||
}
|
||||
return visibleParams.joinToString()
|
||||
}
|
||||
|
||||
private fun createBodyStaticStructReturn(
|
||||
method: CtMethod<*>,
|
||||
params: List<CtParameter<*>>,
|
||||
defaults: IntArray
|
||||
): String {
|
||||
val fieldName = createStaticStructFieldName(method)
|
||||
return buildString {
|
||||
append("${fieldName}.$PTR_JVM_FIELD = ${method.getJniName()}(")
|
||||
append(joinInBodyParams(params, defaults))
|
||||
appendLine(");")
|
||||
append("return $fieldName")
|
||||
}
|
||||
}
|
||||
|
||||
private fun createBodyStructReturn(
|
||||
method: CtMethod<*>,
|
||||
params: List<CtParameter<*>>,
|
||||
defaults: IntArray
|
||||
): String {
|
||||
return buildString {
|
||||
append("return new ${method.type.simpleName}(${method.getJniName()}(")
|
||||
append(joinInBodyParams(params, defaults))
|
||||
append("))")
|
||||
}
|
||||
}
|
||||
|
||||
private fun createBodyDstReturn(
|
||||
method: CtMethod<*>,
|
||||
params: List<CtParameter<*>>,
|
||||
defaults: IntArray
|
||||
): String {
|
||||
val type = method.type.simpleName
|
||||
return buildString {
|
||||
appendLine("final $type dst = new ${type}();")
|
||||
append("${method.getJniName()}(dst")
|
||||
joinInBodyParams(params, defaults).let {
|
||||
if (it.isNotEmpty()) {
|
||||
append(", $it")
|
||||
}
|
||||
}
|
||||
appendLine(");")
|
||||
append("return dst")
|
||||
}
|
||||
}
|
||||
|
||||
private fun createMethod(origM: CtMethod<*>, params: List<CtParameter<*>>, defaults: IntArray): CtMethod<*> {
|
||||
val f = origM.factory
|
||||
val newM = f.createMethod<Nothing>()
|
||||
|
||||
if (origM.isPublic) {
|
||||
newM.addModifier<Nothing>(ModifierKind.PUBLIC)
|
||||
}
|
||||
if (origM.isStatic) {
|
||||
newM.addModifier<Nothing>(ModifierKind.STATIC)
|
||||
}
|
||||
|
||||
if (origM.docComment.isNotBlank()) {
|
||||
newM.setDocComment<Nothing>(origM.docComment)
|
||||
}
|
||||
|
||||
newM.setAnnotations<Nothing>(origM.annotations)
|
||||
newM.setType<Nothing>(origM.type)
|
||||
newM.setSimpleName<Nothing>(origM.getName())
|
||||
|
||||
for ((index, p) in params.withIndex()) {
|
||||
if (defaults.isNotEmpty() && !defaults.contains(index)) {
|
||||
continue
|
||||
}
|
||||
if (p.isType("Void")) {
|
||||
continue
|
||||
}
|
||||
newM.addParameter<Nothing>(f.createParameter<Nothing>().apply {
|
||||
addModifier<Nothing>(ModifierKind.FINAL)
|
||||
setType<Nothing>(p.type)
|
||||
setSimpleName<Nothing>(p.simpleName)
|
||||
})
|
||||
}
|
||||
|
||||
sanitizeDocComment(newM)
|
||||
sanitizeAnnotations(newM)
|
||||
|
||||
newM.setBody<Nothing>(f.createCodeSnippet(
|
||||
buildString {
|
||||
if (origM.isStaticStructReturnValue()) {
|
||||
append(createBodyStaticStructReturn(origM, params, defaults))
|
||||
} else if (DST_RETURN_TYPE_SET.contains(origM.type.simpleName)) {
|
||||
append(createBodyDstReturn(origM, params, defaults))
|
||||
} else if (origM.type.isClass && !origM.type.isPrimitive && !origM.isType("void") && !origM.isType("String") && !origM.isPrimitivePtrType()) {
|
||||
append(createBodyStructReturn(origM, params, defaults))
|
||||
} else {
|
||||
if (!origM.isType("void")) {
|
||||
append("return ")
|
||||
}
|
||||
append("${origM.getJniName()}(")
|
||||
append(joinInBodyParams(params, defaults))
|
||||
append(")")
|
||||
}
|
||||
}
|
||||
))
|
||||
|
||||
return newM
|
||||
}
|
||||
|
||||
private fun methodVecUnwrappedContent(method: CtMethod<*>): String {
|
||||
val newMethod = method.clone()
|
||||
val newParams = mutableListOf<CtParameter<*>>()
|
||||
val vecParamNames = mutableSetOf<String>()
|
||||
|
||||
for (p in newMethod.parameters) {
|
||||
if (p.isType("ImVec2") || p.isType("ImVec4")) {
|
||||
vecParamNames += p.simpleName
|
||||
|
||||
val paramX = p.factory.createParameter<Nothing>()
|
||||
paramX.addModifier<Nothing>(ModifierKind.FINAL)
|
||||
paramX.setType<Nothing>(p.factory.createTypeParam("float"))
|
||||
paramX.setSimpleName<Nothing>("${p.simpleName}X")
|
||||
|
||||
val paramY = paramX.clone()
|
||||
paramY.setSimpleName<Nothing>("${p.simpleName}Y")
|
||||
|
||||
newParams += paramX
|
||||
newParams += paramY
|
||||
|
||||
if (p.isType("ImVec4")) {
|
||||
val paramZ = paramX.clone()
|
||||
paramZ.setSimpleName<Nothing>("${p.simpleName}Z")
|
||||
val paramW = paramX.clone()
|
||||
paramW.setSimpleName<Nothing>("${p.simpleName}W")
|
||||
|
||||
newParams += paramZ
|
||||
newParams += paramW
|
||||
}
|
||||
|
||||
getJDoc(newMethod)?.let { jDoc ->
|
||||
val idx = jDoc.tags.indexOfFirst { it.param == p.simpleName }
|
||||
if (idx != -1) {
|
||||
jDoc.removeTag<Nothing>(idx)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
newParams += p
|
||||
}
|
||||
}
|
||||
|
||||
newMethod.setParameters<Nothing>(newParams)
|
||||
|
||||
val mContentOrig = method.prettyprint()
|
||||
val mContent = newMethod.prettyprint().let {
|
||||
var str = it
|
||||
for (paramName in vecParamNames) {
|
||||
str = str.replace("${paramName}.x, ${paramName}.y", "${paramName}X, ${paramName}Y")
|
||||
str = str.replace("${paramName}.z, ${paramName}.w", "${paramName}Z, ${paramName}W")
|
||||
}
|
||||
str
|
||||
}
|
||||
|
||||
if (mContent != mContentOrig) {
|
||||
return mContent
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
private fun methodRectUnwrappedContent(method: CtMethod<*>): String {
|
||||
val newMethod = method.clone()
|
||||
val newParams = mutableListOf<CtParameter<*>>()
|
||||
val paramNames = mutableSetOf<String>()
|
||||
|
||||
for (p in newMethod.parameters) {
|
||||
if (p.isType("ImRect")) {
|
||||
paramNames += p.simpleName
|
||||
|
||||
val paramMinX = p.factory.createParameter<Nothing>()
|
||||
paramMinX.addModifier<Nothing>(ModifierKind.FINAL)
|
||||
paramMinX.setType<Nothing>(p.factory.createTypeParam("float"))
|
||||
paramMinX.setSimpleName<Nothing>("${p.simpleName}MinX")
|
||||
|
||||
val paramMinY = paramMinX.clone()
|
||||
paramMinY.setSimpleName<Nothing>("${p.simpleName}MinY")
|
||||
|
||||
newParams += paramMinX
|
||||
newParams += paramMinY
|
||||
|
||||
val paramMaxX = paramMinX.clone()
|
||||
paramMaxX.setSimpleName<Nothing>("${p.simpleName}MaxX")
|
||||
val paramMaxY = paramMinX.clone()
|
||||
paramMaxY.setSimpleName<Nothing>("${p.simpleName}MaxY")
|
||||
|
||||
newParams += paramMaxX
|
||||
newParams += paramMaxY
|
||||
} else {
|
||||
newParams += p
|
||||
}
|
||||
}
|
||||
|
||||
newMethod.setParameters<Nothing>(newParams)
|
||||
|
||||
val mContentOrig = method.prettyprint()
|
||||
val mContent = newMethod.prettyprint().let {
|
||||
var str = it
|
||||
for (paramName in paramNames) {
|
||||
str = str.replace(
|
||||
"${paramName}.min.x, ${paramName}.min.y, ${paramName}.max.x, ${paramName}.max.y",
|
||||
"${paramName}MinX, ${paramName}MinY, ${paramName}MaxX, ${paramName}MaxY"
|
||||
)
|
||||
}
|
||||
str
|
||||
}
|
||||
|
||||
if (mContent != mContentOrig) {
|
||||
return mContent
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
private fun methodPlotPointUnwrappedContent(method: CtMethod<*>): String {
|
||||
val newMethod = method.clone()
|
||||
val newParams = mutableListOf<CtParameter<*>>()
|
||||
val paramNames = mutableSetOf<String>()
|
||||
|
||||
for (p in newMethod.parameters) {
|
||||
if (p.isType("ImPlotPoint")) {
|
||||
paramNames += p.simpleName
|
||||
|
||||
val paramX = p.factory.createParameter<Nothing>()
|
||||
paramX.addModifier<Nothing>(ModifierKind.FINAL)
|
||||
paramX.setType<Nothing>(p.factory.createTypeParam("double"))
|
||||
paramX.setSimpleName<Nothing>("${p.simpleName}X")
|
||||
|
||||
val paramY = paramX.clone()
|
||||
paramY.setSimpleName<Nothing>("${p.simpleName}Y")
|
||||
|
||||
newParams += paramX
|
||||
newParams += paramY
|
||||
} else {
|
||||
newParams += p
|
||||
}
|
||||
}
|
||||
|
||||
newMethod.setParameters<Nothing>(newParams)
|
||||
|
||||
val mContentOrig = method.prettyprint()
|
||||
val mContent = newMethod.prettyprint().let {
|
||||
var str = it
|
||||
for (paramName in paramNames) {
|
||||
str = str.replace("${paramName}.x, ${paramName}.y", "${paramName}X, ${paramName}Y")
|
||||
}
|
||||
str
|
||||
}
|
||||
|
||||
if (mContent != mContentOrig) {
|
||||
return mContent
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
private fun methodPlotRangeUnwrappedContent(method: CtMethod<*>): String {
|
||||
val newMethod = method.clone()
|
||||
val newParams = mutableListOf<CtParameter<*>>()
|
||||
val paramNames = mutableSetOf<String>()
|
||||
|
||||
for (p in newMethod.parameters) {
|
||||
if (p.isType("ImPlotRange")) {
|
||||
paramNames += p.simpleName
|
||||
|
||||
val paramX = p.factory.createParameter<Nothing>()
|
||||
paramX.addModifier<Nothing>(ModifierKind.FINAL)
|
||||
paramX.setType<Nothing>(p.factory.createTypeParam("double"))
|
||||
paramX.setSimpleName<Nothing>("${p.simpleName}Min")
|
||||
|
||||
val paramY = paramX.clone()
|
||||
paramY.setSimpleName<Nothing>("${p.simpleName}Max")
|
||||
|
||||
newParams += paramX
|
||||
newParams += paramY
|
||||
} else {
|
||||
newParams += p
|
||||
}
|
||||
}
|
||||
|
||||
newMethod.setParameters<Nothing>(newParams)
|
||||
|
||||
val mContentOrig = method.prettyprint()
|
||||
val mContent = newMethod.prettyprint().let {
|
||||
var str = it
|
||||
for (paramName in paramNames) {
|
||||
str = str.replace("${paramName}.min, ${paramName}.max", "${paramName}Min, ${paramName}Max")
|
||||
}
|
||||
str
|
||||
}
|
||||
|
||||
if (mContent != mContentOrig) {
|
||||
return mContent
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
private fun methodPlotLimitsUnwrappedContent(method: CtMethod<*>): String {
|
||||
val newMethod = method.clone()
|
||||
val newParams = mutableListOf<CtParameter<*>>()
|
||||
val paramNames = mutableSetOf<String>()
|
||||
|
||||
for (p in newMethod.parameters) {
|
||||
if (p.isType("ImPlotLimits")) {
|
||||
paramNames += p.simpleName
|
||||
|
||||
val paramMinX = p.factory.createParameter<Nothing>()
|
||||
paramMinX.addModifier<Nothing>(ModifierKind.FINAL)
|
||||
paramMinX.setType<Nothing>(p.factory.createTypeParam("double"))
|
||||
paramMinX.setSimpleName<Nothing>("${p.simpleName}MinX")
|
||||
|
||||
val paramMinY = paramMinX.clone()
|
||||
paramMinY.setSimpleName<Nothing>("${p.simpleName}MinY")
|
||||
|
||||
newParams += paramMinX
|
||||
newParams += paramMinY
|
||||
|
||||
val paramMaxX = paramMinX.clone()
|
||||
paramMaxX.setSimpleName<Nothing>("${p.simpleName}MaxX")
|
||||
val paramMaxY = paramMinX.clone()
|
||||
paramMaxY.setSimpleName<Nothing>("${p.simpleName}MaxY")
|
||||
|
||||
newParams += paramMaxX
|
||||
newParams += paramMaxY
|
||||
} else {
|
||||
newParams += p
|
||||
}
|
||||
}
|
||||
|
||||
newMethod.setParameters<Nothing>(newParams)
|
||||
|
||||
val mContentOrig = method.prettyprint()
|
||||
val mContent = newMethod.prettyprint().let {
|
||||
var str = it
|
||||
for (paramName in paramNames) {
|
||||
str = str.replace(
|
||||
"${paramName}.x.min, ${paramName}.y.min, ${paramName}.x.max, ${paramName}.y.max",
|
||||
"${paramName}MinX, ${paramName}MinY, ${paramName}MaxX, ${paramName}MaxY"
|
||||
)
|
||||
}
|
||||
str
|
||||
}
|
||||
|
||||
if (mContent != mContentOrig) {
|
||||
return mContent
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
private fun methodCoordinatesUnwrappedContent(method: CtMethod<*>): String {
|
||||
val newMethod = method.clone()
|
||||
val newParams = mutableListOf<CtParameter<*>>()
|
||||
val paramNames = mutableSetOf<String>()
|
||||
|
||||
for (p in newMethod.parameters) {
|
||||
if (p.isType("TextEditorCoordinates")) {
|
||||
paramNames += p.simpleName
|
||||
|
||||
val paramX = p.factory.createParameter<Nothing>()
|
||||
paramX.addModifier<Nothing>(ModifierKind.FINAL)
|
||||
paramX.setType<Nothing>(p.factory.createTypeParam("int"))
|
||||
paramX.setSimpleName<Nothing>("${p.simpleName}Line")
|
||||
|
||||
val paramY = paramX.clone()
|
||||
paramY.setSimpleName<Nothing>("${p.simpleName}Column")
|
||||
|
||||
newParams += paramX
|
||||
newParams += paramY
|
||||
} else {
|
||||
newParams += p
|
||||
}
|
||||
}
|
||||
|
||||
newMethod.setParameters<Nothing>(newParams)
|
||||
|
||||
val mContentOrig = method.prettyprint()
|
||||
val mContent = newMethod.prettyprint().let {
|
||||
var str = it
|
||||
for (paramName in paramNames) {
|
||||
str = str.replace("${paramName}.mLine, ${paramName}.mColumn", "${paramName}Line, ${paramName}Column")
|
||||
}
|
||||
str
|
||||
}
|
||||
|
||||
if (mContent != mContentOrig) {
|
||||
return mContent
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
private fun createMethodDstReturn(
|
||||
mOrig: CtMethod<*>,
|
||||
params: List<CtParameter<*>>,
|
||||
defaults: IntArray
|
||||
): CtMethod<*> {
|
||||
val mNew = mOrig.clone()
|
||||
mNew.setType<Nothing>(mNew.factory.createTypeParam("void"))
|
||||
mNew.addParameterAt<Nothing>(0, mOrig.factory.createParameter<Nothing>().apply {
|
||||
addModifier<Nothing>(ModifierKind.FINAL)
|
||||
setType<Nothing>(factory.createTypeParam(mOrig.type.simpleName))
|
||||
setSimpleName<Nothing>("dst")
|
||||
})
|
||||
mNew.setBody<Nothing>(mOrig.factory.createCodeSnippet(
|
||||
buildString {
|
||||
append("${mOrig.getJniName()}(dst")
|
||||
joinInBodyParams(params, defaults).let {
|
||||
if (it.isNotEmpty()) {
|
||||
append(", $it")
|
||||
}
|
||||
}
|
||||
append(")")
|
||||
}
|
||||
))
|
||||
return mNew
|
||||
}
|
||||
|
||||
private fun createMethodVecValueReturn(
|
||||
vecVal: String,
|
||||
mOrig: CtMethod<*>,
|
||||
params: List<CtParameter<*>>,
|
||||
defaults: IntArray
|
||||
): CtMethod<*> {
|
||||
val mNew = mOrig.clone()
|
||||
mNew.setSimpleName<Nothing>(mOrig.getName() + vecVal.capitalize())
|
||||
mNew.type.setSimpleName<Nothing>("float")
|
||||
mNew.setBody<Nothing>(mOrig.factory.createCodeSnippetStatement().apply {
|
||||
setValue<Nothing>(buildString {
|
||||
append("return ")
|
||||
append("${mNew.getJniName()}(")
|
||||
append(joinInBodyParams(params, defaults))
|
||||
append(")")
|
||||
})
|
||||
})
|
||||
return mNew
|
||||
}
|
||||
|
||||
private fun transformMethodToContent(
|
||||
mOrig: CtMethod<*>,
|
||||
params: List<CtParameter<*>> = emptyList(),
|
||||
defaults: IntArray = intArrayOf()
|
||||
): List<String> {
|
||||
val methods = mutableListOf<String>()
|
||||
val mNew = createMethod(mOrig, params, defaults)
|
||||
|
||||
methods += mNew.prettyprint()
|
||||
|
||||
if (params.find { it.isType("ImVec2") || it.isType("ImVec4") } != null) {
|
||||
methodVecUnwrappedContent(mNew).takeIf(String::isNotEmpty)?.run(methods::add)
|
||||
}
|
||||
|
||||
if (params.find { it.isType("ImRect") } != null) {
|
||||
methodRectUnwrappedContent(mNew).takeIf(String::isNotEmpty)?.run(methods::add)
|
||||
}
|
||||
|
||||
if (params.find { it.isType("ImPlotPoint") } != null) {
|
||||
methodPlotPointUnwrappedContent(mNew).takeIf(String::isNotEmpty)?.run(methods::add)
|
||||
}
|
||||
|
||||
if (params.find { it.isType("ImPlotRange") } != null) {
|
||||
methodPlotRangeUnwrappedContent(mNew).takeIf(String::isNotEmpty)?.run(methods::add)
|
||||
}
|
||||
|
||||
if (params.find { it.isType("ImPlotLimits") } != null) {
|
||||
methodPlotLimitsUnwrappedContent(mNew).takeIf(String::isNotEmpty)?.run(methods::add)
|
||||
}
|
||||
|
||||
if (params.find { it.isType("TextEditorCoordinates") } != null) {
|
||||
methodCoordinatesUnwrappedContent(mNew).takeIf(String::isNotEmpty)?.run(methods::add)
|
||||
}
|
||||
|
||||
if (mOrig.isType("ImVec2") || mOrig.isType("ImVec4")) {
|
||||
methods += createMethodVecValueReturn("x", mNew, params, defaults).prettyprint()
|
||||
methods += createMethodVecValueReturn("y", mNew, params, defaults).prettyprint()
|
||||
if (mOrig.isType("ImVec4")) {
|
||||
methods += createMethodVecValueReturn("z", mNew, params, defaults).prettyprint()
|
||||
methods += createMethodVecValueReturn("w", mNew, params, defaults).prettyprint()
|
||||
}
|
||||
}
|
||||
|
||||
if (DST_RETURN_TYPE_SET.contains(mOrig.type.simpleName)) {
|
||||
methods += createMethodDstReturn(mNew, params, defaults).prettyprint()
|
||||
}
|
||||
|
||||
return methods
|
||||
}
|
||||
|
||||
fun jvmMethodContent(method: CtMethod<*>): List<String> {
|
||||
val addContent = mutableListOf<String>()
|
||||
|
||||
if (method.isStaticStructReturnValue()) {
|
||||
val staticStructFieldName = createStaticStructFieldName(method)
|
||||
addContent += createStaticStructField(method.type.simpleName, staticStructFieldName)
|
||||
}
|
||||
|
||||
val methods = mutableListOf<String>()
|
||||
|
||||
for (paramsSize in (findFirstOptParam(method)..method.parameters.size)) {
|
||||
val params = method.parameters.subList(0, paramsSize)
|
||||
methods += transformMethodToContent(method, params)
|
||||
}
|
||||
|
||||
for (defaults in findDefaultsCombinations(method)) {
|
||||
methods += transformMethodToContent(method, method.parameters, defaults)
|
||||
}
|
||||
|
||||
return addContent + methods
|
||||
}
|
||||
|
||||
private fun CtMethod<*>.isStaticStructReturnValue(): Boolean {
|
||||
return !isType("void") && getAnnotation(A_NAME_RETURN_VALUE)?.getValueAsObject(A_VALUE_IS_STATIC) == true
|
||||
}
|
||||
|
||||
private fun createFieldGetContent(field: CtField<*>): List<String> {
|
||||
val f = field.factory
|
||||
|
||||
val getAccessor = f.createMethod<Nothing>()
|
||||
getAccessor.setParent<Nothing>(field.parent)
|
||||
getAccessor.setSimpleName<Nothing>("get${field.simpleName}")
|
||||
getAccessor.setType<Nothing>(field.type)
|
||||
getAccessor.addModifier<Nothing>(ModifierKind.PUBLIC)
|
||||
getAccessor.setDocComment<Nothing>(field.docComment)
|
||||
getAccessor.setAnnotations<Nothing>(field.annotations)
|
||||
|
||||
val result = jvmMethodContent(getAccessor).toMutableList()
|
||||
|
||||
if (field.hasAnnotation(A_NAME_TYPE_ARRAY)) {
|
||||
when (val arrayType = field.getAnnotation(A_NAME_TYPE_ARRAY)!!.getValueAsString(A_VALUE_TYPE)) {
|
||||
"boolean", "short", "int", "float", "double", "long" -> {
|
||||
val newM = getAccessor.clone()
|
||||
newM.setType<Nothing>(f.createTypeParam(arrayType))
|
||||
newM.addParameter<Nothing>(f.createParameter<Nothing>().apply {
|
||||
setType<Nothing>(f.createTypeParam("int"))
|
||||
setSimpleName<Nothing>("idx")
|
||||
})
|
||||
result += jvmMethodContent(newM)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
private fun createFieldSetContent(field: CtField<*>): List<String> {
|
||||
val f = field.factory
|
||||
|
||||
val setAccessor = f.createMethod<Nothing>()
|
||||
setAccessor.setType<Nothing>(f.createTypeParam("void"))
|
||||
setAccessor.setParent<Nothing>(field.parent)
|
||||
setAccessor.setSimpleName<Nothing>("set${field.simpleName}")
|
||||
setAccessor.addModifier<Nothing>(ModifierKind.PUBLIC)
|
||||
setAccessor.setDocComment<Nothing>(field.docComment)
|
||||
setAccessor.setAnnotations<Nothing>(field.annotations)
|
||||
|
||||
val valueParam = f.createParameter<Nothing>().apply {
|
||||
setType<Nothing>(field.type)
|
||||
setSimpleName<Nothing>("value")
|
||||
}
|
||||
|
||||
val result = transformMethodToContent(setAccessor, listOf(valueParam)).toMutableList()
|
||||
|
||||
if (field.hasAnnotation(A_NAME_TYPE_ARRAY)) {
|
||||
when (val arrayType = field.getAnnotation(A_NAME_TYPE_ARRAY)!!.getValueAsString(A_VALUE_TYPE)) {
|
||||
"boolean", "short", "int", "float", "double", "long" -> {
|
||||
val newM = setAccessor.clone()
|
||||
newM.parameters.clear()
|
||||
newM.addParameter<Nothing>(f.createParameter<Nothing>().apply {
|
||||
setType<Nothing>(f.createTypeParam("int"))
|
||||
setSimpleName<Nothing>("idx")
|
||||
})
|
||||
newM.addParameter<Nothing>(f.createParameter<Nothing>().apply {
|
||||
setType<Nothing>(f.createTypeParam(arrayType))
|
||||
setSimpleName<Nothing>("value")
|
||||
})
|
||||
result += transformMethodToContent(newM, newM.parameters)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
private fun createFieldFlagUtils(field: CtField<*>): List<String> {
|
||||
val mAdd = field.factory.createMethod<Nothing>()
|
||||
mAdd.setSimpleName<Nothing>("add${field.simpleName}")
|
||||
mAdd.setType<Nothing>(field.factory.createTypeParam("void"))
|
||||
mAdd.addModifier<Nothing>(ModifierKind.PUBLIC)
|
||||
mAdd.setDocComment<Nothing>(field.docComment)
|
||||
mAdd.addParameter<Nothing>(field.factory.createParameter<Nothing>().apply {
|
||||
addModifier<Nothing>(ModifierKind.FINAL)
|
||||
setType<Nothing>(field.factory.createTypeParam("int"))
|
||||
setSimpleName<Nothing>("flags")
|
||||
})
|
||||
mAdd.setBody<Nothing>(field.factory.createCodeSnippet("set${field.simpleName}(get${field.simpleName}() | flags)"))
|
||||
|
||||
val mRemove = mAdd.clone()
|
||||
mRemove.setSimpleName<Nothing>("remove${field.simpleName}")
|
||||
mRemove.setBody<Nothing>(field.factory.createCodeSnippet("set${field.simpleName}(get${field.simpleName}() & ~(flags))"))
|
||||
|
||||
val mHas = mAdd.clone()
|
||||
mHas.setSimpleName<Nothing>("has${field.simpleName}")
|
||||
mHas.setType<Nothing>(field.factory.createTypeParam("boolean"))
|
||||
mHas.setBody<Nothing>(field.factory.createCodeSnippet("return (get${field.simpleName}() & flags) != 0"))
|
||||
|
||||
val utilMethods = mutableListOf<String>()
|
||||
val bindingFieldAnnotation = field.getAnnotation(A_NAME_BINDING_FIELD)
|
||||
|
||||
if (bindingFieldAnnotation?.containsValue(A_VALUE_ACCESSORS, A_TYPE_VALUE_ACCESSOR_SETTER) == true) {
|
||||
utilMethods += mAdd.prettyprint()
|
||||
utilMethods += mRemove.prettyprint()
|
||||
}
|
||||
|
||||
if (bindingFieldAnnotation?.containsValue(A_VALUE_ACCESSORS, A_TYPE_VALUE_ACCESSOR_GETTER) == true) {
|
||||
utilMethods += mHas.prettyprint()
|
||||
}
|
||||
|
||||
return utilMethods
|
||||
}
|
||||
|
||||
fun jvmFieldContent(field: CtField<*>): List<String> {
|
||||
val fields = mutableListOf<String>()
|
||||
val a = field.getAnnotation(A_NAME_BINDING_FIELD)
|
||||
if (a?.containsValue(A_VALUE_ACCESSORS, A_TYPE_VALUE_ACCESSOR_GETTER) == true) {
|
||||
fields += createFieldGetContent(field)
|
||||
}
|
||||
if (a?.containsValue(A_VALUE_ACCESSORS, A_TYPE_VALUE_ACCESSOR_SETTER) == true) {
|
||||
fields += createFieldSetContent(field)
|
||||
}
|
||||
if (field.getAnnotation(A_NAME_BINDING_FIELD)?.getValueAsObject(A_VALUE_IS_FLAG) == true) {
|
||||
fields += createFieldFlagUtils(field)
|
||||
}
|
||||
return fields
|
||||
}
|
||||
|
||||
private fun getJDoc(method: CtMethod<*>): CtJavaDoc? {
|
||||
return method.comments.filterIsInstance<CtJavaDoc>().firstOrNull()
|
||||
}
|
||||
|
||||
private fun sanitizeDocComment(method: CtMethod<*>) {
|
||||
val jDoc = getJDoc(method) ?: return
|
||||
val paramNames = method.parameters.map { it.simpleName }
|
||||
jDoc.setTags<Nothing>(jDoc.tags.filter { it.type != CtJavaDocTag.TagType.PARAM || paramNames.contains(it.param) })
|
||||
}
|
||||
|
||||
private fun sanitizeAnnotations(method: CtMethod<*>) {
|
||||
method.setAnnotations<Nothing>(method.annotations.filter { !CLEANUP_ANNOTATIONS_LIST.contains(it.name) })
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package tool.generator.api.task
|
||||
|
||||
import org.gradle.api.DefaultTask
|
||||
import org.gradle.api.tasks.Internal
|
||||
import org.gradle.api.tasks.TaskAction
|
||||
import spoon.Launcher
|
||||
import spoon.support.compiler.FileSystemFolder
|
||||
import tool.generator.api.BindingSourceProcessor
|
||||
import tool.generator.api.ExcludedSourceProcessor
|
||||
|
||||
open class GenerateApi : DefaultTask() {
|
||||
@Internal
|
||||
override fun getGroup() = "build"
|
||||
|
||||
@Internal
|
||||
override fun getDescription() = "Generate API for native binaries."
|
||||
|
||||
private val genSrcDir = project.file("src/main/java")
|
||||
private val genDstDir = project.file("src/generated/java")
|
||||
|
||||
@TaskAction
|
||||
fun run() {
|
||||
logger.info("Generating API...")
|
||||
|
||||
logger.info("Removing old generated files in $genDstDir...")
|
||||
project.file(genDstDir).deleteRecursively()
|
||||
|
||||
logger.info("Copying raw sources...")
|
||||
logger.info("| from: $genSrcDir")
|
||||
logger.info("| into: $genDstDir")
|
||||
project.copy {
|
||||
from(genSrcDir)
|
||||
into(genDstDir)
|
||||
}
|
||||
|
||||
logger.info("Processing generated sources...")
|
||||
|
||||
val launcher = Launcher()
|
||||
launcher.addInputResource(FileSystemFolder(genDstDir))
|
||||
val model = launcher.buildModel()
|
||||
model.allTypes.filter(BindingSourceProcessor::isProcessable).forEach {
|
||||
BindingSourceProcessor(it).process()
|
||||
}
|
||||
model.allTypes.filter(ExcludedSourceProcessor::isProcessable).forEach {
|
||||
ExcludedSourceProcessor(it).process()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,186 @@
|
||||
package tool.generator.api
|
||||
|
||||
import spoon.reflect.code.CtCodeSnippetStatement
|
||||
import spoon.reflect.code.CtExpression
|
||||
import spoon.reflect.code.CtFieldRead
|
||||
import spoon.reflect.code.CtNewArray
|
||||
import spoon.reflect.declaration.*
|
||||
import spoon.reflect.factory.Factory
|
||||
import spoon.reflect.reference.CtTypeParameterReference
|
||||
import tool.generator.ast.Decl
|
||||
import tool.generator.ast.DeclContainer
|
||||
|
||||
const val A_NAME_BINDING_SOURCE = "BindingSource"
|
||||
const val A_NAME_BINDING_METHOD = "BindingMethod"
|
||||
const val A_NAME_BINDING_FIELD = "BindingField"
|
||||
const val A_NAME_BINDING_AST_ENUM = "BindingAstEnum"
|
||||
const val A_NAME_OPT_ARG = "OptArg"
|
||||
const val A_NAME_EXCLUDED_SOURCE = "ExcludedSource"
|
||||
const val A_NAME_RETURN_VALUE = "ReturnValue"
|
||||
const val A_NAME_ARG_VALUE = "ArgValue"
|
||||
const val A_NAME_TYPE_ARRAY = "TypeArray"
|
||||
|
||||
const val A_VALUE_CALL_PTR = "callPtr"
|
||||
const val A_VALUE_CALL_OPERATOR = "callOperator"
|
||||
const val A_VALUE_CALL_PREFIX = "callPrefix"
|
||||
const val A_VALUE_CALL_SUFFIX = "callSuffix"
|
||||
const val A_VALUE_STATIC_CAST = "staticCast"
|
||||
const val A_VALUE_REINTERPRET_CAST = "reinterpretCast"
|
||||
const val A_VALUE_IS_STATIC = "isStatic"
|
||||
const val A_VALUE_CALL_VALUE = "callValue"
|
||||
const val A_VALUE_NAME = "name"
|
||||
const val A_VALUE_CALL_NAME = "callName"
|
||||
const val A_VALUE_ACCESSORS = "accessors"
|
||||
const val A_VALUE_IS_FLAG = "isFlag"
|
||||
const val A_VALUE_TYPE = "type"
|
||||
const val A_VALUE_SIZE = "size"
|
||||
const val A_VALUE_FILE = "file"
|
||||
const val A_VALUE_QUAL_TYPE = "qualType"
|
||||
const val A_VALUE_SANITIZE_NAME = "sanitizeName"
|
||||
|
||||
const val A_TYPE_VALUE_ACCESSOR_SETTER = "SETTER"
|
||||
const val A_TYPE_VALUE_ACCESSOR_GETTER = "GETTER"
|
||||
|
||||
const val PTR_JVM_FIELD = "ptr"
|
||||
const val PTR_JNI_CAST = "intptr_t"
|
||||
const val PTR_JNI_THIS = "THIS"
|
||||
|
||||
const val PARAM_ARR_LEN_POSTFIX = "Count"
|
||||
|
||||
val CLEANUP_ANNOTATIONS_LIST = listOf(
|
||||
A_NAME_BINDING_SOURCE,
|
||||
A_NAME_BINDING_METHOD,
|
||||
A_NAME_BINDING_FIELD,
|
||||
A_NAME_RETURN_VALUE,
|
||||
A_NAME_OPT_ARG,
|
||||
A_NAME_ARG_VALUE,
|
||||
A_NAME_TYPE_ARRAY,
|
||||
A_NAME_BINDING_AST_ENUM,
|
||||
)
|
||||
|
||||
val PRIMITIVE_PTR_TYPECAST_MAP = mapOf(
|
||||
"boolean[]" to "bool*",
|
||||
"byte[]" to "char*",
|
||||
"short[]" to "short*",
|
||||
"int[]" to "int*",
|
||||
"float[]" to "float*",
|
||||
"long[]" to "long*",
|
||||
"double[]" to "double*",
|
||||
)
|
||||
|
||||
val DST_RETURN_TYPE_SET = setOf(
|
||||
"ImVec2",
|
||||
"ImVec4",
|
||||
"ImRect",
|
||||
"ImPlotPoint",
|
||||
"ImPlotRange",
|
||||
"ImPlotLimits",
|
||||
"TextEditorCoordinates",
|
||||
)
|
||||
|
||||
fun CtElement.hasAnnotation(annotationName: String): Boolean {
|
||||
return getAnnotation(annotationName) != null
|
||||
}
|
||||
|
||||
fun CtElement.getAnnotation(annotationName: String): CtAnnotation<*>? {
|
||||
return annotations.find { a -> a?.name == annotationName }
|
||||
}
|
||||
|
||||
fun CtAnnotation<*>.containsValue(annotationField: String, value: String): Boolean {
|
||||
val v = getValue<CtExpression<Any>>(annotationField)
|
||||
if (v is CtFieldRead) {
|
||||
return v.prettyprint().contains(value)
|
||||
}
|
||||
if (v is CtNewArray) {
|
||||
return v.elements.find { it.prettyprint().contains(value) } != null
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
fun Factory.createTypeParam(name: String): CtTypeParameterReference = createTypeParameterReference().apply {
|
||||
setSimpleName<Nothing>(name)
|
||||
}
|
||||
|
||||
fun Factory.createCodeSnippet(code: String): CtCodeSnippetStatement = createCodeSnippetStatement().apply {
|
||||
setValue<Nothing>(code)
|
||||
}
|
||||
|
||||
fun CtTypedElement<*>.isType(type: String): Boolean = this.type.simpleName == type
|
||||
|
||||
fun CtTypedElement<*>.isPrimitivePtrType(): Boolean {
|
||||
return PRIMITIVE_PTR_TYPECAST_MAP.containsKey(type.simpleName)
|
||||
}
|
||||
|
||||
fun CtTypeInformation.isPtrClass(): Boolean {
|
||||
return getDeclaredOrInheritedField(PTR_JVM_FIELD) != null
|
||||
}
|
||||
|
||||
fun CtMethod<*>.getJniName(): String = "n${simpleName.capitalize()}"
|
||||
|
||||
fun CtMethod<*>.getName(): String {
|
||||
return getAnnotation(A_NAME_BINDING_METHOD)?.getValueAsString(A_VALUE_NAME)?.takeIf(String::isNotEmpty)
|
||||
?: simpleName.decapitalize()
|
||||
}
|
||||
|
||||
fun CtField<*>.getCallName(): String {
|
||||
return getAnnotation(A_NAME_BINDING_FIELD)?.getValueAsString(A_VALUE_CALL_NAME)?.takeIf(String::isNotEmpty)
|
||||
?: simpleName
|
||||
}
|
||||
|
||||
fun findDefaultsCombinations(method: CtMethod<*>): List<IntArray> {
|
||||
fun findDefaults(params: List<CtParameter<*>>): Map<Int, String> {
|
||||
val defaults = mutableMapOf<Int, String>()
|
||||
for ((index, p) in params.withIndex()) {
|
||||
p.getAnnotation(A_NAME_OPT_ARG)?.let { a ->
|
||||
val value = a.getValueAsString(A_VALUE_CALL_VALUE)
|
||||
if (value.isNotEmpty()) {
|
||||
defaults[index] = value
|
||||
}
|
||||
}
|
||||
}
|
||||
return defaults
|
||||
}
|
||||
|
||||
fun concatNextParam0(combinations: MutableList<IntArray>, idx0: Int, idxN: Int) {
|
||||
val p1 = method.parameters[idx0]
|
||||
val p2 = method.parameters[idxN]
|
||||
if (p1.type.simpleName != p2.type.simpleName) {
|
||||
combinations.add(((0 until idx0) + (idxN until method.parameters.size)).toIntArray())
|
||||
if (p2.getAnnotation(A_NAME_OPT_ARG)?.getValueAsString(A_VALUE_CALL_VALUE)?.isNotEmpty() == true) {
|
||||
concatNextParam0(combinations, idx0, idxN + 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val combinations = mutableListOf<IntArray>()
|
||||
findDefaults(method.parameters).forEach { (idx, _) -> concatNextParam0(combinations, idx, idx + 1) }
|
||||
return combinations
|
||||
}
|
||||
|
||||
fun findFirstOptParam(method: CtMethod<*>): Int {
|
||||
if (method.parameters.isEmpty()) {
|
||||
return method.parameters.size
|
||||
}
|
||||
for ((index, p) in method.parameters.withIndex()) {
|
||||
if (p.hasAnnotation(A_NAME_OPT_ARG)) {
|
||||
return index
|
||||
}
|
||||
}
|
||||
return method.parameters.size
|
||||
}
|
||||
|
||||
fun Collection<Decl>.filterDecls0(filter: (Decl) -> Boolean): Collection<Decl> {
|
||||
val result = mutableListOf<Decl>()
|
||||
fun filter0(decls: Collection<Decl>) {
|
||||
decls.forEach { decl ->
|
||||
if (filter(decl)) {
|
||||
result += decl
|
||||
}
|
||||
if (decl is DeclContainer) {
|
||||
filter0(decl.decls)
|
||||
}
|
||||
}
|
||||
}
|
||||
filter0(this)
|
||||
return result
|
||||
}
|
||||
Reference in new issue
Block a user