Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add BerTlvItemList annotation. #22

Draft
wants to merge 5 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions app/src/main/java/dev/keiji/tlv/sample/ConstructedData.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@ data class ConstructedData(

@BerTlvItem(tag = [0x03], typeConverter = ByteTypeConverter::class)
var data4: Byte? = null,

@BerTlvItemList(tag = [0x05], typeConverter = ByteTypeConverter::class)
var data5: ArrayList<Byte>? = null,

@BerTlvItemList(tag = [0x06])
var data6: ArrayList<PrimitiveMultiBytesTagData>? = null
) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
Expand All @@ -33,6 +39,8 @@ data class ConstructedData(
if (data2 != other.data2) return false
if (data3 != other.data3) return false
if (data4 != other.data4) return false
if (data5 != other.data5) return false
if (data6 != other.data6) return false

return true
}
Expand All @@ -43,6 +51,8 @@ data class ConstructedData(
result = 31 * result + (data2?.hashCode() ?: 0)
result = 31 * result + (data3?.hashCode() ?: 0)
result = 31 * result + (data4 ?: 0)
result = 31 * result + (data5?.hashCode() ?: 0)
result = 31 * result + (data6?.hashCode() ?: 0)
return result
}
}
36 changes: 36 additions & 0 deletions app/src/test/java/dev/keiji/tlv/sample/ConstructedDataTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,17 @@ class ConstructedDataTest {
it.data2 = true
it.data3 = "Hello!"
it.data4 = 0x07F
it.data5 = arrayListOf(0x02, 0x03)
it.data6 = arrayListOf(
PrimitiveMultiBytesTagData(
data1 = byteArrayOf(0x07, 0x08),
data2 = byteArrayOf(0x09, 0x0A, 0x0B)
),
PrimitiveMultiBytesTagData(
data1 = byteArrayOf(0x0C, 0x0D),
data2 = byteArrayOf(0x0E, 0x0F, 0x10)
),
)
}

val expected = byteArrayOf(
Expand All @@ -27,6 +38,13 @@ class ConstructedDataTest {
0x1F, 0x03, 0x01, 0xFF.toByte(),
0x1F, 0x81.toByte(), 0x03, 0x06, 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x21,
0x03, 0x01, 0x7F,
0x05, 0x01, 0x02,
0x05, 0x01, 0x03,
0x06, 0x0C,
0x1F, 0x01, 0x02, 0x07, 0x08,
0x1F, 0x81.toByte(), 0x01, 0x03, 0x09, 0x0A, 0x0B,
0x06, 0x0C, 0x1F, 0x01, 0x02, 0x0C, 0x0D,
0x1F, 0x81.toByte(), 0x01, 0x03, 0x0E, 0x0F, 0x10,
)

val actual = ByteArrayOutputStream().use {
Expand All @@ -48,6 +66,13 @@ class ConstructedDataTest {
0x1F, 0x03, 0x01, 0xFF.toByte(),
0x1F, 0x81.toByte(), 0x03, 0x06, 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x21,
0x03, 0x01, 0x7F,
0x05, 0x01, 0x02,
0x05, 0x01, 0x03,
0x06, 0x0C,
0x1F, 0x01, 0x02, 0x07, 0x08,
0x1F, 0x81.toByte(), 0x01, 0x03, 0x09, 0x0A, 0x0B,
0x06, 0x0C, 0x1F, 0x01, 0x02, 0x0C, 0x0D,
0x1F, 0x81.toByte(), 0x01, 0x03, 0x0E, 0x0F, 0x10,
)

val expected = ConstructedData().also {
Expand All @@ -59,6 +84,17 @@ class ConstructedDataTest {
it.data2 = true
it.data3 = "Hello!"
it.data4 = 0x7F
it.data5 = arrayListOf(0x02, 0x03)
it.data6 = arrayListOf(
PrimitiveMultiBytesTagData(
data1 = byteArrayOf(0x07, 0x08),
data2 = byteArrayOf(0x09, 0x0A, 0x0B)
),
PrimitiveMultiBytesTagData(
data1 = byteArrayOf(0x0C, 0x0D),
data2 = byteArrayOf(0x0E, 0x0F, 0x10)
),
)
}

val actual = ConstructedData().also { it.readFrom(data) }
Expand Down
4 changes: 2 additions & 2 deletions tlv-ksp/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ plugins {
}

dependencies {
implementation("dev.keiji.tlv:tlv:${rootProject.ext.versionCode}")
// implementation project(path: ':tlv')
// implementation("dev.keiji.tlv:tlv:${rootProject.ext.versionCode}")
implementation project(path: ':tlv')

implementation "com.google.devtools.ksp:symbol-processing-api:1.7.10-1.0.6"

Expand Down
44 changes: 32 additions & 12 deletions tlv-ksp/src/main/java/dev/keiji/tlv/BerTlvDecoderProcessor.kt
Original file line number Diff line number Diff line change
Expand Up @@ -17,22 +17,30 @@
package dev.keiji.tlv

import com.google.devtools.ksp.processing.*
import com.google.devtools.ksp.symbol.*
import com.google.devtools.ksp.symbol.KSAnnotated
import com.google.devtools.ksp.symbol.KSClassDeclaration
import com.google.devtools.ksp.symbol.KSPropertyDeclaration
import com.google.devtools.ksp.symbol.KSVisitorVoid
import com.google.devtools.ksp.validate
import java.lang.StringBuilder
import kotlin.collections.HashMap

class BerTlvDecoderProcessor(
private val codeGenerator: CodeGenerator,
private val logger: KSPLogger,
) : SymbolProcessor {

private lateinit var berTlvClasses: Sequence<KSAnnotated>
private lateinit var berTlvClassesStringList: List<String>

override fun process(resolver: Resolver): List<KSAnnotated> {
berTlvClasses = resolver.getSymbolsWithAnnotation(BerTlv::class.qualifiedName!!)
val ret = berTlvClasses.filter { !it.validate() }.toList()

berTlvClassesStringList = berTlvClasses
.filter { it is KSClassDeclaration && it.validate() }
.map { it as KSClassDeclaration }
.mapNotNull { it.qualifiedName?.asString() }
.toList()

berTlvClasses
.filter { it is KSClassDeclaration && it.validate() }
.forEach { it.accept(BerTlvVisitor(), Unit) }
Expand All @@ -50,9 +58,7 @@ class BerTlvDecoderProcessor(
val annotatedProperties = classDeclaration.getAllProperties()
.filter { it.validate() }
.filter { prop ->
prop.annotations.any { anno ->
anno.shortName.asString() == BerTlvItem::class.simpleName
}
prop.annotations.any { anno -> isTargetAnnotation(anno) }
}

processClass(classDeclaration, annotatedProperties, logger)
Expand Down Expand Up @@ -135,17 +141,31 @@ fun ${classDeclaration.simpleName.asString()}.readFrom(data: ByteArray) {
sb.append(" // Do nothing\n")

annotatedProperties.forEach { prop ->
val annotationName = getAnnotationName(prop, logger)
val tag = getTagAsString(prop, logger)
val qualifiedName = getQualifiedName(prop, logger)
val converterVariableName = converterTable[qualifiedName]

val className = prop.type.resolve().declaration.qualifiedName?.asString()

sb.append(" } else if (${tag}.contentEquals(tag)) {\n")

val decClass = prop.type.resolve().declaration
if (berTlvClasses.contains(decClass)) {
val className = decClass.simpleName.asString()
sb.append(" this@readFrom.${prop.simpleName.asString()} = ${className}().also { it.readFrom(data) }\n")
} else {
sb.append(" this@readFrom.${prop.simpleName.asString()} = ${converterVariableName}.convertFromByteArray(data)\n")
if (annotationName == BerTlvItem::class.simpleName) {
if (berTlvClassesStringList.contains(className)) {
sb.append(" this@readFrom.${prop.simpleName.asString()} = ${className}().also { it.readFrom(data) }\n")
} else {
sb.append(" this@readFrom.${prop.simpleName.asString()} = ${converterVariableName}.convertFromByteArray(data)\n")
}
} else if (annotationName == BerTlvItemList::class.simpleName) {
val genericClassName = getGenericsTypeNameFromList(prop)

sb.append(" val list = this@readFrom.${prop.simpleName.asString()} ?: ${className}()\n")
if (berTlvClassesStringList.contains(genericClassName)) {
sb.append(" list.add(${genericClassName}().also { it.readFrom(data) })\n")
} else {
sb.append(" list.add(${converterVariableName}.convertFromByteArray(data))\n")
}
sb.append(" this@readFrom.${prop.simpleName.asString()} = list\n")
}
}

Expand Down
58 changes: 42 additions & 16 deletions tlv-ksp/src/main/java/dev/keiji/tlv/BerTlvEncoderProcessor.kt
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,20 @@ class BerTlvEncoderProcessor(
private val codeGenerator: CodeGenerator,
private val logger: KSPLogger,
) : SymbolProcessor {

private lateinit var berTlvClasses: Sequence<KSAnnotated>
private lateinit var berTlvClassesStringList: List<String>

override fun process(resolver: Resolver): List<KSAnnotated> {
berTlvClasses = resolver.getSymbolsWithAnnotation(BerTlv::class.qualifiedName!!)
val ret = berTlvClasses.filter { !it.validate() }.toList()

berTlvClassesStringList = berTlvClasses
.filter { it is KSClassDeclaration && it.validate() }
.map { it as KSClassDeclaration }
.mapNotNull { it.qualifiedName?.asString() }
.toList()

berTlvClasses
.filter { it is KSClassDeclaration && it.validate() }
.forEach { it.accept(BerTlvVisitor(), Unit) }
Expand All @@ -51,9 +59,7 @@ class BerTlvEncoderProcessor(
val annotatedProperties = classDeclaration.getAllProperties()
.filter { it.validate() }
.filter { prop ->
prop.annotations.any { anno ->
anno.shortName.asString() == BerTlvItem::class.simpleName
}
prop.annotations.any { anno -> isTargetAnnotation(anno) }
}

validateAnnotations(annotatedProperties, logger)
Expand Down Expand Up @@ -133,25 +139,45 @@ fun ${classDeclaration.simpleName.asString()}.writeTo(outputStream: OutputStream
sb.append("\n")

annotatedProperties.forEach { prop ->
val annotationName = getAnnotationName(prop, logger)
val tag = getTagAsString(prop, logger)
val qualifiedName = getQualifiedName(prop, logger)
val converterVariableName = converterTable[qualifiedName]

val className = prop.type.resolve().declaration.qualifiedName?.asString()
val propName =
prop.simpleName.asString() + if (prop.type.resolve().isMarkedNullable) "?" else ""

val decClass = prop.type.resolve().declaration
if (berTlvClasses.contains(decClass)) {
sb.append(" ${propName}.also {\n")
sb.append(" val data = ByteArrayOutputStream().let { baos ->\n")
sb.append(" ${propName}.writeTo(baos)\n")
sb.append(" baos.toByteArray()\n")
sb.append(" }\n")
sb.append(" BerTlvEncoder.writeTo(${tag}, data, outputStream)\n")
sb.append(" }\n")
} else {
sb.append(" ${propName}.also {\n")
sb.append(" BerTlvEncoder.writeTo(${tag}, ${converterVariableName}.convertToByteArray(it), outputStream)\n")
sb.append(" }\n")
if (annotationName == BerTlvItem::class.simpleName) {
if (berTlvClassesStringList.contains(className)) {
sb.append(" ${propName}.also {\n")
sb.append(" val data = ByteArrayOutputStream().let { baos ->\n")
sb.append(" ${propName}.writeTo(baos)\n")
sb.append(" baos.toByteArray()\n")
sb.append(" }\n")
sb.append(" BerTlvEncoder.writeTo(${tag}, data, outputStream)\n")
sb.append(" }\n")
} else {
sb.append(" ${propName}.also {\n")
sb.append(" BerTlvEncoder.writeTo(${tag}, ${converterVariableName}.convertToByteArray(it), outputStream)\n")
sb.append(" }\n")
}
} else if (annotationName == BerTlvItemList::class.simpleName) {
val genericClassName = getGenericsTypeNameFromList(prop)

if (berTlvClassesStringList.contains(genericClassName)) {
sb.append(" ${propName}.forEach {\n")
sb.append(" val data = ByteArrayOutputStream().let { baos ->\n")
sb.append(" it.writeTo(baos)\n")
sb.append(" baos.toByteArray()\n")
sb.append(" }\n")
sb.append(" BerTlvEncoder.writeTo(${tag}, data, outputStream)\n")
sb.append(" }\n")
} else {
sb.append(" ${propName}.forEach {\n")
sb.append(" BerTlvEncoder.writeTo(${tag}, ${converterVariableName}.convertToByteArray(it), outputStream)\n")
sb.append(" }\n")
}
}
}

Expand Down
34 changes: 31 additions & 3 deletions tlv-ksp/src/main/java/dev/keiji/tlv/Utils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package dev.keiji.tlv

import com.google.devtools.ksp.processing.KSPLogger
import com.google.devtools.ksp.symbol.KSAnnotation
import com.google.devtools.ksp.symbol.KSPropertyDeclaration
import com.google.devtools.ksp.symbol.KSType
import com.google.devtools.ksp.validate
Expand Down Expand Up @@ -47,7 +48,7 @@ internal fun getOrder(prop: KSPropertyDeclaration): Int {

val berTlvItem = prop.annotations
.filter { it.validate() }
.firstOrNull { it.shortName.asString() == BerTlvItem::class.simpleName }
.firstOrNull { isTargetAnnotation(it) }
berTlvItem
?: throw IllegalArgumentException("BerTlv annotation must be exist.")

Expand Down Expand Up @@ -178,7 +179,7 @@ internal fun getTagAsByteArray(prop: KSPropertyDeclaration): ByteArray {

val berTlvItem = prop.annotations
.filter { it.validate() }
.firstOrNull { it.shortName.asString() == BerTlvItem::class.simpleName }
.firstOrNull { isTargetAnnotation(it) }
berTlvItem
?: throw IllegalArgumentException("BerTlv annotation must be exist.")

Expand All @@ -205,6 +206,12 @@ internal fun getTagAsByteArray(prop: KSPropertyDeclaration): ByteArray {
return tagAsByteList.toByteArray()
}

internal fun isTargetAnnotation(it: KSAnnotation): Boolean {
val existBerTlvItem = it.shortName.asString() == BerTlvItem::class.simpleName
val existBerTlvItemList = it.shortName.asString() == BerTlvItemList::class.simpleName
return existBerTlvItem || existBerTlvItemList
}

internal fun getTagAsString(
prop: KSPropertyDeclaration,
logger: KSPLogger,
Expand All @@ -222,7 +229,7 @@ internal fun getQualifiedName(

val berTlvItem = prop.annotations
.filter { it.validate() }
.firstOrNull { it.shortName.asString() == BerTlvItem::class.simpleName }
.firstOrNull { isTargetAnnotation(it) }
berTlvItem
?: throw IllegalArgumentException("BerTlv annotation must be exist.")

Expand All @@ -237,4 +244,25 @@ internal fun getQualifiedName(
return argumentValue.declaration.qualifiedName!!.asString()
}

internal fun getAnnotationName(
prop: KSPropertyDeclaration,
logger: KSPLogger,
): String {
val berTlvItem = prop.annotations
.filter { it.validate() }
.firstOrNull { isTargetAnnotation(it) }
berTlvItem
?: throw IllegalArgumentException("BerTlv annotation must be exist.")

return berTlvItem.shortName.asString()
}

internal fun generateVariableName(qualifiedName: String): String = qualifiedName.replace(".", "_")

fun getGenericsTypeNameFromList(prop: KSPropertyDeclaration): String? {
val argument = prop.type.resolve().arguments
if (argument.size == 0) {
return null
}
return argument[0].type?.resolve()?.declaration?.qualifiedName?.asString()
}
7 changes: 7 additions & 0 deletions tlv/src/main/java/dev/keiji/tlv/Annotations.kt
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,10 @@ annotation class BerTlvItem(
val typeConverter: KClass<out AbsTypeConverter<*>> = NopConverter::class,
val order: Int = 0
)

@Target(AnnotationTarget.FIELD)
annotation class BerTlvItemList(
val tag: ByteArray,
val typeConverter: KClass<out AbsTypeConverter<*>> = NopConverter::class,
val order: Int = 0
)