Skip to content

Commit 6993464

Browse files
nomisRevraulraja
authored andcommitted
Autofold (#448)
* Annotation * Processor
1 parent 185cf36 commit 6993464

File tree

5 files changed

+154
-0
lines changed

5 files changed

+154
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package kategory.fold
2+
3+
import kategory.common.utils.ClassOrPackageDataWrapper
4+
import javax.lang.model.element.TypeElement
5+
6+
data class AnnotatedFold(
7+
val type: TypeElement,
8+
val typeParams: List<String>,
9+
val classData: ClassOrPackageDataWrapper.Class,
10+
val targets: List<Variant>
11+
)
12+
data class Variant(val fullName: String, val typeParams: List<String>, val simpleName: String)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package kategory.fold
2+
3+
import kategory.autofold
4+
5+
val foldAnnotationKClass = autofold::class
6+
val foldAnnotationClass = foldAnnotationKClass.java
7+
val foldAnnotationName = "@" + foldAnnotationClass.simpleName
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package kategory.fold
2+
3+
import kategory.common.utils.fullName
4+
import me.eugeniomarletti.kotlin.metadata.escapedClassName
5+
import java.io.File
6+
7+
class AutoFoldFileGenerator(
8+
private val annotatedList: Collection<AnnotatedFold>,
9+
private val generatedDir: File
10+
) {
11+
12+
fun generate() = annotatedList.map(this::processElement)
13+
.map { (element, fold) ->
14+
"${foldAnnotationClass.simpleName}.${element.type.simpleName.toString().toLowerCase()}.kt" to
15+
fileHeader(element.classData.`package`.escapedClassName) + fold
16+
}.map { (name, fileString) -> File(generatedDir, name).writeText(fileString) }
17+
18+
private fun processElement(annotatedFold: AnnotatedFold): Pair<AnnotatedFold, String> =
19+
annotatedFold to annotatedFold.targets.let { targets ->
20+
val sourceClassName = annotatedFold.classData.fullName.escapedClassName
21+
val sumTypeParams = typeParams(annotatedFold.typeParams)
22+
val returnType = getFoldType(annotatedFold.typeParams)
23+
val functionTypeParams = functionTypeParams(annotatedFold.typeParams, returnType)
24+
25+
"""inline fun $functionTypeParams $sourceClassName$sumTypeParams.fold(
26+
|${params(targets, returnType)}
27+
|): $returnType = when (this) {
28+
|${patternMatching(targets)}
29+
|}
30+
""".trimMargin()
31+
}
32+
33+
fun typeParams(params: List<String>): String =
34+
if (params.isNotEmpty()) params.joinToString(prefix = "<", postfix = ">")
35+
else ""
36+
37+
fun params(variants: List<Variant>, returnType: String): String = variants.joinToString(transform = { variant ->
38+
" crossinline ${variant.simpleName.decapitalize()}: (${variant.fullName.escapedClassName}${typeParams(variant.typeParams)}) -> $returnType"
39+
}, separator = ",\n")
40+
41+
fun patternMatching(variants: List<Variant>): String = variants.joinToString(transform = { variant ->
42+
" is ${variant.fullName.escapedClassName} -> ${variant.simpleName.decapitalize().escapedClassName}(this)"
43+
}, separator = "\n")
44+
45+
fun functionTypeParams(params: List<String>, returnType: String): String =
46+
if (params.isEmpty()) ""
47+
else params.joinToString(prefix = "<", postfix = ", $returnType>")
48+
49+
fun getFoldType(params: List<String>): String {
50+
fun check(param: String, next: List<String>): String = (param[0] + 1).let {
51+
if (next.contains(it.toString())) check(next.firstOrNull() ?: "", next.drop(1))
52+
else it.toString()
53+
}
54+
55+
return check(params.firstOrNull() ?: "", params.drop(1))
56+
}
57+
58+
fun fileHeader(packageName: String): String =
59+
"""package $packageName
60+
|
61+
|""".trimMargin()
62+
63+
}
64+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package kategory.fold
2+
3+
import com.google.auto.service.AutoService
4+
import kategory.common.utils.AbstractProcessor
5+
import kategory.common.utils.asClassOrPackageDataWrapper
6+
import kategory.common.utils.isSealed
7+
import kategory.common.utils.knownError
8+
import me.eugeniomarletti.kotlin.metadata.KotlinClassMetadata
9+
import me.eugeniomarletti.kotlin.metadata.kotlinMetadata
10+
import java.io.File
11+
import javax.annotation.processing.Processor
12+
import javax.annotation.processing.RoundEnvironment
13+
import javax.lang.model.SourceVersion
14+
import javax.lang.model.element.TypeElement
15+
import javax.lang.model.element.TypeParameterElement
16+
17+
@AutoService(Processor::class)
18+
class AutoFoldProcessor : AbstractProcessor() {
19+
20+
private val annotatedList = mutableListOf<AnnotatedFold>()
21+
22+
override fun getSupportedSourceVersion(): SourceVersion = SourceVersion.latestSupported()
23+
24+
override fun getSupportedAnnotationTypes(): Set<String> = setOf(foldAnnotationClass.canonicalName)
25+
26+
/**
27+
* Processor entry point
28+
*/
29+
override fun onProcess(annotations: Set<TypeElement>, roundEnv: RoundEnvironment) {
30+
annotatedList += roundEnv
31+
.getElementsAnnotatedWith(foldAnnotationClass)
32+
.map { element ->
33+
when {
34+
element.let { it.kotlinMetadata as? KotlinClassMetadata }?.data?.classProto?.isSealed == true -> {
35+
val (nameResolver, classProto) = element.kotlinMetadata.let { it as KotlinClassMetadata }.data
36+
37+
AnnotatedFold(
38+
element as TypeElement,
39+
element.typeParameters.map(TypeParameterElement::toString),
40+
element.kotlinMetadata
41+
.let { it as KotlinClassMetadata }
42+
.data
43+
.asClassOrPackageDataWrapper(elementUtils.getPackageOf(element).toString()),
44+
classProto.sealedSubclassFqNameList
45+
.map(nameResolver::getString)
46+
.map { it.replace('/', '.') }
47+
.map {
48+
Variant(it,
49+
elementUtils.getTypeElement(it).typeParameters.map(TypeParameterElement::toString),
50+
it.substringAfterLast("."))
51+
}
52+
)
53+
}
54+
55+
else -> knownError("Generation of fold is only supported for sealed classes.")
56+
}
57+
}
58+
59+
if (roundEnv.processingOver()) {
60+
val generatedDir = File(this.generatedDir!!, foldAnnotationClass.simpleName).also { it.mkdirs() }
61+
AutoFoldFileGenerator(annotatedList, generatedDir).generate()
62+
}
63+
}
64+
65+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package kategory
2+
3+
@Retention(AnnotationRetention.RUNTIME)
4+
@Target(AnnotationTarget.CLASS)
5+
@MustBeDocumented
6+
annotation class autofold

0 commit comments

Comments
 (0)