Skip to content

Commit

Permalink
ADD: jsonReader support null validator
Browse files Browse the repository at this point in the history
  • Loading branch information
RicardoJiang committed Nov 12, 2023
1 parent e02198d commit 0e33962
Show file tree
Hide file tree
Showing 4 changed files with 96 additions and 15 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ import org.jetbrains.kotlin.ir.builders.irCall
import org.jetbrains.kotlin.ir.builders.irElseBranch
import org.jetbrains.kotlin.ir.builders.irEquals
import org.jetbrains.kotlin.ir.builders.irGet
import org.jetbrains.kotlin.ir.builders.irGetField
import org.jetbrains.kotlin.ir.builders.irNotEquals
import org.jetbrains.kotlin.ir.builders.irNull
import org.jetbrains.kotlin.ir.builders.irReturn
import org.jetbrains.kotlin.ir.builders.irSetField
import org.jetbrains.kotlin.ir.builders.irString
Expand All @@ -38,6 +41,7 @@ import org.jetbrains.kotlin.ir.builders.irWhile
import org.jetbrains.kotlin.ir.declarations.IrClass
import org.jetbrains.kotlin.ir.declarations.IrField
import org.jetbrains.kotlin.ir.declarations.IrFunction
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
import org.jetbrains.kotlin.ir.expressions.IrBranch
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol
Expand All @@ -58,6 +62,8 @@ internal class KudosFromJsonFunctionBuilder(
private val irClass: IrClass,
private val irFunction: IrFunction,
private val pluginContext: IrPluginContext,
private val kudosStatusField: IrField?,
private val validatorFunction: IrSimpleFunction?,
startOffset: Int = SYNTHETIC_OFFSET,
endOffset: Int = SYNTHETIC_OFFSET,
) : IrBlockBodyBuilder(pluginContext, Scope(irFunction.symbol), startOffset, endOffset) {
Expand Down Expand Up @@ -105,7 +111,20 @@ internal class KudosFromJsonFunctionBuilder(
branches.add(
irBranch(
irEquals(irGet(name), irString(field.name.asString())),
irSetField(irFunction.irThis(), field, getNextValue(field)),
irBlock {
+irSetField(irFunction.irThis(), field, getNextValue(field))
if (kudosStatusField!=null) {
+irCall(
pluginContext.referenceFunctions(
CallableId(FqName("java.util"), FqName("Map"), Name.identifier("put")),
).first(),
).apply {
putValueArgument(0, irString(field.name.asString()))
putValueArgument(1, irNotEquals(irGetField(irFunction.irThis(), field), irNull()))
dispatchReceiver = irGetField(irFunction.irThis(), kudosStatusField)
}
}
}
),
)
}
Expand All @@ -130,6 +149,12 @@ internal class KudosFromJsonFunctionBuilder(
).apply {
dispatchReceiver = irGet(jsonReader)
}
if (validatorFunction != null && kudosStatusField != null) {
+irCall(validatorFunction.symbol).apply {
putValueArgument(0, irGetField(irFunction.irThis(), kudosStatusField))
dispatchReceiver = irFunction.irThis()
}
}
+irReturn(
irFunction.irThis(),
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,27 +18,32 @@ package com.kanyun.kudos.compiler

import com.kanyun.kudos.compiler.KudosNames.ADAPTER_FACTORY_NAME
import com.kanyun.kudos.compiler.KudosNames.JSON_ADAPTER_NAME
import com.kanyun.kudos.compiler.KudosNames.KUDOS_FIELD_STATUS_MAP_IDENTIFIER
import com.kanyun.kudos.compiler.KudosNames.KUDOS_VALIDATOR_NAME
import com.kanyun.kudos.compiler.options.Options
import com.kanyun.kudos.compiler.utils.addOverride
import com.kanyun.kudos.compiler.utils.hasKudosAnnotation
import com.kanyun.kudos.compiler.utils.irThis
import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext
import org.jetbrains.kotlin.backend.common.lower.DeclarationIrBuilder
import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.ir.IrStatement
import org.jetbrains.kotlin.ir.builders.IrBlockBodyBuilder
import org.jetbrains.kotlin.ir.builders.IrBuilderWithScope
import org.jetbrains.kotlin.ir.builders.Scope
import org.jetbrains.kotlin.ir.builders.declarations.addField
import org.jetbrains.kotlin.ir.builders.declarations.addValueParameter
import org.jetbrains.kotlin.ir.builders.declarations.buildConstructor
import org.jetbrains.kotlin.ir.builders.irCall
import org.jetbrains.kotlin.ir.builders.irExprBody
import org.jetbrains.kotlin.ir.builders.irGet
import org.jetbrains.kotlin.ir.builders.irGetField
import org.jetbrains.kotlin.ir.builders.irSetField
import org.jetbrains.kotlin.ir.declarations.IrClass
import org.jetbrains.kotlin.ir.declarations.IrConstructor
import org.jetbrains.kotlin.ir.declarations.IrField
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
import org.jetbrains.kotlin.ir.expressions.IrGetValue
import org.jetbrains.kotlin.ir.expressions.IrStatementOrigin
import org.jetbrains.kotlin.ir.expressions.impl.IrClassReferenceImpl
Expand Down Expand Up @@ -90,8 +95,8 @@ class KudosIrClassTransformer(
generateJsonAdapter()
}
generateNoArgConstructor()
generateValidator()
generateFromJson()
val validatorFunction = generateValidator()
generateFromJson(validatorFunction)
}

private fun generateJsonAdapter() {
Expand Down Expand Up @@ -201,7 +206,7 @@ class KudosIrClassTransformer(
}
}

private fun generateValidator() {
private fun generateValidator(): IrSimpleFunction? {
val nonDefaults = ArrayList<String>()
val collections = ArrayList<IrField>()
val arrays = ArrayList<IrField>()
Expand Down Expand Up @@ -231,7 +236,7 @@ class KudosIrClassTransformer(
}
}

if (nonDefaults.isEmpty() && collections.isEmpty() && arrays.isEmpty()) return
if (nonDefaults.isEmpty() && collections.isEmpty() && arrays.isEmpty()) return null

val statusType = context.irBuiltIns.mapClass.typeWith(
context.irBuiltIns.stringType,
Expand All @@ -245,7 +250,7 @@ class KudosIrClassTransformer(
}

if (validateFunction?.isFakeOverride == false) {
return
return validateFunction
} else if (validateFunction?.isFakeOverride == true) {
irClass.declarations.remove(validateFunction)
}
Expand Down Expand Up @@ -347,12 +352,13 @@ class KudosIrClassTransformer(
}
}
}
return validateFunction
}

private fun needsNoargConstructor(declaration: IrClass): Boolean =
declaration.kind == ClassKind.CLASS &&
declaration.hasKudosAnnotation() &&
declaration.constructors.none { it.isZeroParameterConstructor() }
declaration.hasKudosAnnotation() &&
declaration.constructors.none { it.isZeroParameterConstructor() }

// Returns true if this constructor is callable with no arguments by JVM rules, i.e. will have descriptor `()V`.
private fun IrConstructor.isZeroParameterConstructor(): Boolean {
Expand All @@ -361,13 +367,39 @@ class KudosIrClassTransformer(
} && (valueParameters.isEmpty() || isPrimary || hasAnnotation(JvmNames.JVM_OVERLOADS_FQ_NAME))
}

private fun generateFromJson() {
irClass.functions.singleOrNull {
it.name.identifier == "fromJson"
}?.takeIf {
it.body == null
}?.let { function ->
KudosFromJsonFunctionBuilder(irClass, function, context).generateBody()
private fun generateFromJson(validatorFunction: IrSimpleFunction?) {
if (irClass.hasKudosAnnotation()) {
val fieldType = context.irBuiltIns.mapClass.typeWith(
context.irBuiltIns.stringClass.defaultType,
context.irBuiltIns.booleanClass.defaultType
)
val initExpression = context.referenceFunctions(
CallableId(FqName("kotlin.collections"), Name.identifier("hashMapOf")),
).first()
val kudosStatusField = if (validatorFunction != null) {
irClass.addField(
KUDOS_FIELD_STATUS_MAP_IDENTIFIER,
fieldType
).apply {
initializer = DeclarationIrBuilder(
context,
symbol,
symbol.owner.startOffset,
symbol.owner.endOffset
).run {
irExprBody(irCall(initExpression))
}
}
} else {
null
}
irClass.functions.singleOrNull {
it.name.identifier == "fromJson"
}?.takeIf {
it.body == null
}?.let { function ->
KudosFromJsonFunctionBuilder(irClass, function, context, kudosStatusField, validatorFunction).generateBody()
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ object KudosNames {
// Name.identifier
val KUDOS_FROM_JSON_IDENTIFIER = Name.identifier("fromJson")
val JSON_READER_IDENTIFIER = Name.identifier("jsonReader")
val KUDOS_FIELD_STATUS_MAP_IDENTIFIER = Name.identifier("kudosFieldStatusMap")

val CONTAINER_FQ_NAMES = setOf(
"kotlin.Array",
Expand Down
23 changes: 23 additions & 0 deletions kudos-compiler/testData/jsonReader/simple.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,20 @@
// SOURCE
// FILE: Main.kt
package com.kanyun.kudos.test

import com.kanyun.kudos.annotations.Kudos

@Kudos
class Desc(val descDetail: String)

@Kudos
class Project(val projectName: String, val projectId: Int, val tags: List<String>,val desc: Desc)

// EXPECT
// FILE: compiles.log
OK
// FILE: Main.kt.ir
package com.kanyun.kudos.test
@Kudos
class Desc(val descDetail: String) : KudosValidator, KudosJsonAdapter<Desc> {
override fun fromJson(jsonReader: JsonReader): Desc {
Expand All @@ -10,13 +24,15 @@ class Desc(val descDetail: String) : KudosValidator, KudosJsonAdapter<Desc> {
when {
tmp0 == "descDetail" -> {
<this>.descDetail = jsonReader.nextString()
<this>.kudosFieldStatusMap.put("descDetail", <this>.descDetail != null)
}
else -> {
jsonReader.skipValue()
}
}
}
jsonReader.endObject()
validate(<this>.kudosFieldStatusMap)
return <this>
}
constructor{
Expand All @@ -26,6 +42,7 @@ class Desc(val descDetail: String) : KudosValidator, KudosJsonAdapter<Desc> {
override fun validate(status: Map<String, Boolean>) {
validateField("descDetail", status)
}
private var kudosFieldStatusMap: Map<String, Boolean> = hashMapOf()
}
@Kudos
class Project(val projectName: String, val projectId: Int, val tags: List<String>, val desc: Desc) : KudosValidator, KudosJsonAdapter<Project> {
Expand All @@ -36,22 +53,27 @@ class Project(val projectName: String, val projectId: Int, val tags: List<String
when {
tmp0 == "projectName" -> {
<this>.projectName = jsonReader.nextString()
<this>.kudosFieldStatusMap.put("projectName", <this>.projectName != null)
}
tmp0 == "projectId" -> {
<this>.projectId = jsonReader.nextInt()
<this>.kudosFieldStatusMap.put("projectId", <this>.projectId != null)
}
tmp0 == "tags" -> {
<this>.tags = parseKudosObject(jsonReader, ParameterizedTypeImpl(List<String>::class.javaObjectType, arrayOf(String::class.javaObjectType)))
<this>.kudosFieldStatusMap.put("tags", <this>.tags != null)
}
tmp0 == "desc" -> {
<this>.desc = parseKudosObject(jsonReader, Desc::class.javaObjectType)
<this>.kudosFieldStatusMap.put("desc", <this>.desc != null)
}
else -> {
jsonReader.skipValue()
}
}
}
jsonReader.endObject()
validate(<this>.kudosFieldStatusMap)
return <this>
}
constructor{
Expand All @@ -65,4 +87,5 @@ class Project(val projectName: String, val projectId: Int, val tags: List<String
validateField("desc", status)
validateCollection("tags", <this>.tags, "List")
}
private var kudosFieldStatusMap: Map<String, Boolean> = hashMapOf()
}

0 comments on commit 0e33962

Please sign in to comment.