nitro-codegen
Version:
The code-generator for react-native-nitro-modules.
190 lines (162 loc) • 5.75 kB
text/typescript
import { NitroConfig } from '../../config/NitroConfig.js'
import { capitalizeName, indent } from '../../utils.js'
import { includeHeader } from '../c++/includeNitroHeader.js'
import {
createFileMetadataString,
isNotDuplicate,
toReferenceType,
} from '../helpers.js'
import type { SourceFile } from '../SourceFile.js'
import { type VariantType } from '../types/VariantType.js'
import { KotlinCxxBridgedType } from './KotlinCxxBridgedType.js'
export function createKotlinVariant(variant: VariantType): SourceFile[] {
const jsName = variant.variants.map((v) => v.getCode('kotlin')).join('|')
const kotlinName = variant.getAliasName('kotlin')
const namespace = `J${kotlinName}_impl`
const innerClasses = variant.cases.map(([label, v]) => {
const innerName = capitalizeName(label)
return `
@DoNotStrip
data class ${innerName}(@DoNotStrip val value: ${v.getCode('kotlin')}): ${kotlinName}()
`.trim()
})
const packageName = NitroConfig.getAndroidPackage('java/kotlin')
const getterCases = variant.cases.map(([label]) => {
const innerName = capitalizeName(label)
return `is ${innerName} -> value as? T`
})
const isFunctions = variant.cases.map(([label]) => {
const innerName = capitalizeName(label)
return `
val is${innerName}: Boolean
get() = this is ${innerName}
`.trim()
})
const createFunctions = variant.cases.map(([label, v]) => {
const innerName = capitalizeName(label)
return `
@JvmStatic
@DoNotStrip
fun create(value: ${v.getCode('kotlin')}): ${kotlinName} = ${innerName}(value)
`.trim()
})
const code = `
${createFileMetadataString(`${kotlinName}.kt`)}
package ${packageName}
import com.facebook.proguard.annotations.DoNotStrip
/**
* Represents the TypeScript variant "${jsName}".
*/
@Suppress("ClassName")
@DoNotStrip
sealed class ${kotlinName} {
${indent(innerClasses.join('\n'), ' ')}
inline fun <reified T> getAs(): T? = when (this) {
${indent(getterCases.join('\n'), ' ')}
}
${indent(isFunctions.join('\n'), ' ')}
companion object {
${indent(createFunctions.join('\n'), ' ')}
}
}
`.trim()
const cxxNamespace = NitroConfig.getCxxNamespace('c++')
const jniClassDescriptor = NitroConfig.getAndroidPackage(
'c++/jni',
kotlinName
)
const cppCreateFuncs = variant.variants.map((v, i) => {
const bridge = new KotlinCxxBridgedType(v)
return `
static jni::local_ref<J${kotlinName}> create_${i}(${bridge.asJniReferenceType('alias')} value) {
static const auto method = javaClassStatic()->getStaticMethod<J${kotlinName}(${bridge.asJniReferenceType('alias')})>("create");
return method(javaClassStatic(), value);
}
`.trim()
})
const variantCases = variant.variants.map((v, i) => {
const bridge = new KotlinCxxBridgedType(v)
return `case ${i}: return create_${i}(${bridge.parseFromCppToKotlin(`std::get<${i}>(variant)`, 'c++')});`
})
const cppGetIfs = variant.cases.map(([label, v]) => {
const innerName = capitalizeName(label)
const bridge = new KotlinCxxBridgedType(v)
return `
if (isInstanceOf(${namespace}::${innerName}::javaClassStatic())) {
auto jniValue = static_cast<const ${namespace}::${innerName}*>(this)->getValue();
return ${bridge.parseFromKotlinToCpp('jniValue', 'c++')};
}
`.trim()
})
const cppInnerClasses = variant.cases.map(([label, v]) => {
const bridge = new KotlinCxxBridgedType(v)
const innerName = capitalizeName(label)
const descriptor = NitroConfig.getAndroidPackage(
'c++/jni',
`${kotlinName}$${innerName}`
)
return `
class ${innerName}: public jni::JavaClass<${innerName}, J${kotlinName}> {
public:
static auto constexpr kJavaDescriptor = "L${descriptor};";
[[nodiscard]] ${bridge.asJniReferenceType('local')} getValue() const {
static const auto field = javaClassStatic()->getField<${bridge.getTypeCode('c++')}>("value");
return getFieldValue(field);
}
};
`.trim()
})
const includes = new KotlinCxxBridgedType(variant)
.getRequiredImports()
.filter((i) => i.name !== `J${kotlinName}.hpp`)
.map((i) => includeHeader(i, true))
.filter(isNotDuplicate)
const fbjniCode = `
${createFileMetadataString(`J${kotlinName}.hpp`)}
#pragma once
#include <fbjni/fbjni.h>
#include <variant>
${includes.join('\n')}
namespace ${cxxNamespace} {
using namespace facebook;
/**
* The C++ JNI bridge between the C++ std::variant and the Java class "${kotlinName}".
*/
class J${kotlinName}: public jni::JavaClass<J${kotlinName}> {
public:
static auto constexpr kJavaDescriptor = "L${jniClassDescriptor};";
${indent(cppCreateFuncs.join('\n'), ' ')}
static jni::local_ref<J${kotlinName}> fromCpp(${toReferenceType(variant.getCode('c++'))} variant) {
switch (variant.index()) {
${indent(variantCases.join('\n'), ' ')}
default: throw std::invalid_argument("Variant holds unknown index! (" + std::to_string(variant.index()) + ")");
}
}
[[nodiscard]] ${variant.getCode('c++')} toCpp() const;
};
namespace ${namespace} {
${indent(cppInnerClasses.join('\n\n'), ' ')}
} // namespace ${namespace}
${variant.getCode('c++')} J${kotlinName}::toCpp() const {
${indent(cppGetIfs.join(' else '), ' ')}
throw std::invalid_argument("Variant is unknown Kotlin instance!");
}
} // namespace ${cxxNamespace}
`.trim()
const files: SourceFile[] = []
files.push({
content: code,
language: 'kotlin',
name: `${kotlinName}.kt`,
subdirectory: NitroConfig.getAndroidPackageDirectory(),
platform: 'android',
})
files.push({
content: fbjniCode,
language: 'c++',
name: `J${kotlinName}.hpp`,
subdirectory: [],
platform: 'android',
})
return files
}