generate.TypeScriptGenerator.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of yass Show documentation
Show all versions of yass Show documentation
Yet Another Service Solution
package ch.softappeal.yass.generate.ts
import ch.softappeal.yass.generate.Generator
import ch.softappeal.yass.generate.Out
import ch.softappeal.yass.generate.getMethods
import ch.softappeal.yass.generate.getServiceDescs
import ch.softappeal.yass.generate.isRootClass
import ch.softappeal.yass.generate.iterate
import ch.softappeal.yass.ownFields
import ch.softappeal.yass.remote.Services
import ch.softappeal.yass.remote.SimpleMethodMapperFactory
import ch.softappeal.yass.serialize.fast.BaseTypeSerializer
import ch.softappeal.yass.serialize.fast.BooleanSerializer
import ch.softappeal.yass.serialize.fast.ByteArraySerializer
import ch.softappeal.yass.serialize.fast.ClassTypeSerializer
import ch.softappeal.yass.serialize.fast.DoubleSerializer
import ch.softappeal.yass.serialize.fast.FastSerializer
import ch.softappeal.yass.serialize.fast.FieldDesc
import ch.softappeal.yass.serialize.fast.FirstTypeId
import ch.softappeal.yass.serialize.fast.ListTypeDesc
import ch.softappeal.yass.serialize.fast.StringSerializer
import ch.softappeal.yass.serialize.fast.TypeDesc
import ch.softappeal.yass.serialize.fast.primitiveWrapperType
import java.lang.reflect.Modifier
import java.lang.reflect.ParameterizedType
import java.lang.reflect.Type
import java.lang.reflect.TypeVariable
import java.nio.file.Path
import java.util.HashSet
import java.util.LinkedHashMap
class ExternalDesc(internal val name: String, internal val typeDescHolder: String)
val BooleanDesc = TypeDesc(FirstTypeId, BooleanSerializer)
val DoubleDesc = TypeDesc(FirstTypeId + 1, DoubleSerializer)
val StringDesc = TypeDesc(FirstTypeId + 2, StringSerializer)
val BytesDesc = TypeDesc(FirstTypeId + 3, ByteArraySerializer)
const val FirstDescId = FirstTypeId + 4
@SafeVarargs
fun baseTypeSerializers(vararg serializers: BaseTypeSerializer<*>): List> {
val h = mutableListOf(
BooleanDesc.serializer as BaseTypeSerializer<*>,
DoubleDesc.serializer as BaseTypeSerializer<*>,
StringDesc.serializer as BaseTypeSerializer<*>,
BytesDesc.serializer as BaseTypeSerializer<*>
)
h.addAll(serializers)
return h
}
@SafeVarargs
fun baseTypeDescs(vararg descs: TypeDesc): Collection {
val d = mutableListOf(BooleanDesc, DoubleDesc, StringDesc, BytesDesc)
d.addAll(descs)
return d
}
private fun nullable(type: String): String = "yass.Nullable<$type>"
/**
* You must use the "-parameters" option for javac to get the real method parameter names.
* additionalTypeCode: see [pull request](https://github.com/softappeal/yass/pull/4)
*/
class TypeScriptGenerator @JvmOverloads constructor(
rootPackage: String, serializer: FastSerializer, initiator: Services?, acceptor: Services?,
includeFile: Path, externalTypes: Map, ExternalDesc>, contractFile: Path, additionalTypeCode: String? = null
) : Generator(rootPackage, serializer, initiator, acceptor) {
init {
object : Out(contractFile) {
val type2id = LinkedHashMap, Int>()
val visitedClasses = HashSet>()
init {
id2typeSerializer.forEach { id, typeSerializer -> if (id >= FirstDescId) type2id[typeSerializer.type] = id }
includeFile(includeFile)
@Suppress("UNCHECKED_CAST") id2typeSerializer.values
.map { it.type }
.filter { it.isEnum }
.forEach { generateEnum(it as Class>) }
id2typeSerializer.values
.filter { it is ClassTypeSerializer }
.forEach { generateClass(it.type) }
interfaces.forEach { generateInterface(it) }
generateServices(initiator, "initiator")
generateServices(acceptor, "acceptor")
tabs("export const SERIALIZER = new yass.FastSerializer(")
inc()
iterate(type2id.keys, { print(",") }, { type ->
println()
tabs(jsType(type, false))
})
println()
dec()
tabsln(");")
close()
}
fun jsType(type: Class<*>, externalName: Boolean): String {
val externalDesc = externalTypes[primitiveWrapperType(type)]
if (externalDesc != null) return if (externalName) externalDesc.name else externalDesc.typeDescHolder
checkType(type)
check(!type.isArray) { "illegal type ${type.canonicalName} (use List instead [])" }
return qualifiedName(type)
}
fun generateType(type: Class<*>, typeGenerator: (type: String) -> Unit) {
checkType(type)
val jsType = qualifiedName(type)
val dot = jsType.lastIndexOf('.')
val name = type.simpleName
if (dot < 0) {
typeGenerator(name)
} else {
tabsln("export namespace ${jsType.substring(0, dot)} {")
inc()
typeGenerator(name)
dec()
tabsln("}")
}
println()
}
fun generateEnum(type: Class>) = generateType(type) { name ->
tabsln("export class $name extends yass.Enum {")
inc()
additionalTypeCode()
for (e in type.enumConstants) tabsln("static readonly ${e.name} = new $name(${e.ordinal}, '${e.name}');")
tabsln("static readonly VALUES = <$name[]>yass.enumValues($name);")
tabsln("static readonly TYPE_DESC = yass.enumDesc(${type2id[type]}, $name);")
dec()
tabsln("}")
}
fun type(type: Type?): String = when {
type is ParameterizedType -> if (type.rawType === List::class.java) {
type(type.actualTypeArguments[0]) + "[]"
} else {
val s = StringBuilder(type(type.rawType))
val actualTypeArguments = type.actualTypeArguments
if (actualTypeArguments.isNotEmpty()) {
s.append('<')
iterate(actualTypeArguments.asList(), { s.append(", ") }, { s.append(type(it)) })
s.append('>')
}
s.toString()
}
type is TypeVariable<*> -> type.name
type === Double::class.javaPrimitiveType || type === Double::class.javaObjectType -> "number"
type === Boolean::class.javaPrimitiveType || type === Boolean::class.javaObjectType -> "boolean"
type === String::class.java -> "string"
type === ByteArray::class.java -> "Uint8Array"
isRootClass((type as Class<*>)) -> "any"
type === Void.TYPE -> "void"
else -> jsType(type, true)
}
fun typeDesc(fieldDesc: FieldDesc): String {
val typeSerializer = fieldDesc.serializer.typeSerializer ?: return "null"
return when {
ListTypeDesc.serializer === typeSerializer -> "yass.LIST_DESC"
BooleanDesc.serializer === typeSerializer -> "yass.BOOLEAN_DESC"
DoubleDesc.serializer === typeSerializer -> "yass.NUMBER_DESC"
StringDesc.serializer === typeSerializer -> "yass.STRING_DESC"
BytesDesc.serializer === typeSerializer -> "yass.BYTES_DESC"
else -> jsType(typeSerializer.type, false) + ".TYPE_DESC"
}
}
fun generateClass(type: Class) {
if (!visitedClasses.add(type)) return
var sc: Type? = type.genericSuperclass
if (sc is Class<*>) {
if (isRootClass(sc)) sc = null else generateClass(sc)
} else {
generateClass((sc as ParameterizedType).rawType as Class<*>)
}
val superClass = sc
generateType(type) { name ->
tabs("export ${if (Modifier.isAbstract(type.modifiers)) "abstract " else ""}class $name")
val typeParameters = type.typeParameters
if (typeParameters.isNotEmpty()) {
print("<")
iterate(typeParameters.asList(), { print(", ") }, { print(it.name) })
print(">")
}
if (superClass != null) print(" extends ${type(superClass)}")
println(" {")
inc()
additionalTypeCode()
for (field in ownFields(type)) tabsln("${field.name}: ${nullable(type(field.genericType))};")
val id = type2id[type]
if (id != null) {
tabs("static readonly TYPE_DESC = yass.classDesc($id, $name")
inc()
val typeSerializer = id2typeSerializer[id] as ClassTypeSerializer
check(!typeSerializer.graph) { "class '$type' is graph (not implemented in TypeScript)" }
for (fieldDesc in typeSerializer.fieldDescs) {
println(",")
tabs("new yass.FieldDesc(${fieldDesc.id}, '${fieldDesc.serializer.field.name}', ${typeDesc(fieldDesc)})")
}
println()
dec()
tabsln(");")
}
dec()
tabsln("}")
}
}
fun generateInterface(type: Class<*>) {
SimpleMethodMapperFactory.invoke(type) // checks for overloaded methods (JavaScript restriction)
val methods = getMethods(type)
val methodMapper = methodMapper(type)
generateType(type) { name ->
fun generateInterface(name: String, implementation: Boolean) {
tabsln("export namespace ${if (implementation) "impl" else "proxy"} {")
inc()
tabsln("export interface $name {")
inc()
for (method in methods) {
tabs("${method.name}(")
iterate(listOf(*method.parameters), { print(", ") }, { p ->
print("${p.name}: ${nullable(type(p.parameterizedType))}")
})
print("): ")
if (methodMapper.map(method).oneWay) {
print("void")
} else {
val t = nullable(type(method.genericReturnType))
if (implementation) print(t) else print("Promise<$t>")
}
println(";")
}
dec()
tabsln("}")
dec()
tabsln("}")
}
generateInterface(name, false)
generateInterface(name, true)
tabsln("export namespace mapper {")
inc()
tabs("export const $name = new yass.MethodMapper(")
inc()
iterate(methods, { print(",") }, { method ->
println()
val mapping = methodMapper.map(method)
tabs("new yass.MethodMapping('${mapping.method.name}', ${mapping.id}, ${mapping.oneWay})")
})
println()
dec()
tabsln(");")
dec()
tabsln("}")
}
}
fun generateServices(services: Services?, role: String) {
if (services == null) return
tabsln("export namespace $role {")
inc()
for (serviceDesc in getServiceDescs(services)) {
var name = qualifiedName(serviceDesc.contractId.contract)
var namespace = ""
val dot = name.lastIndexOf('.') + 1
if (dot > 0) {
namespace = name.substring(0, dot)
name = name.substring(dot)
}
tabsln(
"export const ${serviceDesc.name} = new yass.ContractId<${namespace}proxy.$name, " +
"${namespace}impl.$name>(${serviceDesc.contractId.id}, ${namespace}mapper.$name);"
)
}
dec()
tabsln("}")
println()
}
fun additionalTypeCode() {
if (additionalTypeCode != null) tabsln(additionalTypeCode)
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy