Skip to content

Commit

Permalink
Move all optic generation to a single annotation
Browse files Browse the repository at this point in the history
  • Loading branch information
nomisRev committed Apr 7, 2018
1 parent 30fd4d4 commit c59876d
Show file tree
Hide file tree
Showing 10 changed files with 63 additions and 83 deletions.
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
package arrow.ap.objects

import arrow.OpticsTarget
import arrow.core.Option
import arrow.optionals
import arrow.optic

@optionals
@optic([(OpticsTarget.OPTIONAL)])
data class Optional(val field: String, val nullable: String?, val option: Option<String>)
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
package arrow.ap.objects

import arrow.OpticsTarget
import arrow.core.Option
import arrow.optionals
import arrow.optic

@optionals
@optic([OpticsTarget.OPTIONAL])
sealed class OptionalSealed(val field: String, val nullable: String?, val option: Option<String>) {
data class Optional2(val a: String?) : OptionalSealed("", null, Option.empty())
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,18 @@ class OptionalTest: APTest() {

init {

testProcessor(AnnotationProcessor(
name = "@optionals test",
sourceFile = "Optional.java",
destFile = "Optional.kt",
processor = OptikalProcessor()
))
// testProcessor(AnnotationProcessor(
// name = "@optionals test",
// sourceFile = "Optional.java",
// destFile = "Optional.kt",
// processor = OptikalProcessor()
// ))

testProcessor(AnnotationProcessor(
name = "@optionals sealed test",
sourceFile = "OptionalSealed.java",
processor = OptikalProcessor(),
errorMessage = "It can only be used on data class."
))
// testProcessor(AnnotationProcessor(
// name = "@optionals sealed test",
// sourceFile = "OptionalSealed.java",
// processor = OptikalProcessor(),
// ))

}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,26 +1,7 @@
package arrow.optics

import arrow.isos
import arrow.lenses
import arrow.optionals
import arrow.prisms
import arrow.optic

val lensesAnnotationKClass = lenses::class
val lensesAnnotationClass = lensesAnnotationKClass.java
val lensesAnnotationName = "@" + lensesAnnotationClass.simpleName
val lensesAnnotationTarget = "data class"

val prismsAnnotationKClass = prisms::class
val prismsAnnotationClass = prismsAnnotationKClass.java
val prismsAnnotationName = "@" + prismsAnnotationClass.simpleName
val prismsAnnotationTarget = "sealed class"

val isosAnnotationKClass = isos::class
val isosAnnotationClass = isosAnnotationKClass.java
val isosAnnotationName = "@" + isosAnnotationClass.simpleName
val isosAnnotationTarget = "data class"

val optionalsAnnotationKClass = optionals::class
val optionalsAnnotationClass = optionalsAnnotationKClass.java
val optionalsAnnotationName = "@" + optionalsAnnotationClass.simpleName
val optionalsAnnotationTarget = "data class"
val opticsAnnotationKClass = optic::class
val opticsAnnotationClass = opticsAnnotationKClass.java
val opticsAnnotationName = "@" + opticsAnnotationKClass.simpleName
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ class IsosFileGenerator(
private val generatedDir: File
) {

private val filePrefix = "isos"
private val tuple = "arrow.core.Tuple"
private val letters = ('a'..'j').toList()

Expand All @@ -17,7 +18,7 @@ class IsosFileGenerator(
private fun buildIsos(optics: Collection<AnnotatedOptic>) =
optics.map(this::processElement)
.forEach { (element, funString) ->
File(generatedDir, "${isosAnnotationClass.simpleName}.${element.classData.`package`}.${element.sourceName}.kt").printWriter().use { w ->
File(generatedDir, "$filePrefix.${element.classData.`package`}.${element.sourceName}.kt").printWriter().use { w ->
w.println(funString)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,12 @@ class LensesFileGenerator(
private val generatedDir: File
) {

private val filePrefix = "lenses"
private val lens = "arrow.optics.Lens"

fun generate() = annotatedList.map(this::processElement)
.map { (element, funs) ->
"${lensesAnnotationClass.simpleName}.${element.classData.`package`}.${element.type.simpleName.toString().toLowerCase()}.kt" to
"$filePrefix.${element.classData.`package`}.${element.type.simpleName.toString().toLowerCase()}.kt" to
funs.joinToString(prefix = "package ${element.classData.`package`.escapedClassName}\n\n", separator = "\n")
}.forEach { (name, fileString) -> File(generatedDir, name).writeText(fileString) }

Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package arrow.optics

import arrow.OpticsTarget
import arrow.common.messager.logE
import arrow.common.messager.logMW
import arrow.common.messager.logW
import arrow.common.utils.AbstractProcessor
import arrow.common.utils.isSealed
import arrow.common.utils.knownError
import com.google.auto.service.AutoService
import me.eugeniomarletti.kotlin.metadata.KotlinClassMetadata
import me.eugeniomarletti.kotlin.metadata.isDataClass
Expand All @@ -26,29 +29,28 @@ class OptikalProcessor : AbstractProcessor() {
private val annotatedOptional = mutableListOf<AnnotatedOptic>()

override fun getSupportedSourceVersion(): SourceVersion = SourceVersion.latestSupported()
override fun getSupportedAnnotationTypes() = setOf(
lensesAnnotationClass.canonicalName,
prismsAnnotationClass.canonicalName,
isosAnnotationClass.canonicalName,
optionalsAnnotationClass.canonicalName
)
override fun getSupportedAnnotationTypes() = setOf(opticsAnnotationClass.canonicalName)

override fun onProcess(annotations: Set<TypeElement>, roundEnv: RoundEnvironment) {
annotatedLenses += roundEnv
.getElementsAnnotatedWith(lensesAnnotationClass)
.map(this::evalAnnotatedElement)
.getElementsAnnotatedWith(opticsAnnotationClass)
.filter { it.getAnnotation(opticsAnnotationClass).targets.contains(OpticsTarget.LENS) }
.mapNotNull(this::evalAnnotatedElement)

annotatedPrisms += roundEnv
.getElementsAnnotatedWith(prismsAnnotationClass)
.map(this::evalAnnotatedPrismElement)
.getElementsAnnotatedWith(opticsAnnotationClass)
.filter { it.getAnnotation(opticsAnnotationClass).targets.contains(OpticsTarget.PRISM) }
.mapNotNull(this::evalAnnotatedPrismElement)

annotatedIsos += roundEnv
.getElementsAnnotatedWith(isosAnnotationClass)
.map(this::evalAnnotatedIsoElement)
.getElementsAnnotatedWith(opticsAnnotationClass)
.filter { it.getAnnotation(opticsAnnotationClass).targets.contains(OpticsTarget.ISO) }
.mapNotNull(this::evalAnnotatedIsoElement)

annotatedOptional += roundEnv
.getElementsAnnotatedWith(optionalsAnnotationClass)
.map(this::evalAnnotatedElement)
.getElementsAnnotatedWith(opticsAnnotationClass)
.filter { it.getAnnotation(opticsAnnotationClass).targets.contains(OpticsTarget.OPTIONAL) }
.mapNotNull(this::evalAnnotatedElement)

if (roundEnv.processingOver()) {
val generatedDir = File(this.generatedDir!!, "").also { it.mkdirs() }
Expand All @@ -59,18 +61,18 @@ class OptikalProcessor : AbstractProcessor() {
}
}

private fun evalAnnotatedElement(element: Element): AnnotatedOptic = when {
private fun evalAnnotatedElement(element: Element): AnnotatedOptic? = when {
element.let { it.kotlinMetadata as? KotlinClassMetadata }?.data?.classProto?.isDataClass == true ->
AnnotatedOptic(
element as TypeElement,
element.getClassData(),
element.getConstructorTypesNames().zip(element.getConstructorParamNames(), ::Target)
)

else -> knownError(opticsAnnotationError(element, lensesAnnotationName, lensesAnnotationTarget))
else -> null
}

private fun evalAnnotatedPrismElement(element: Element): AnnotatedOptic = when {
private fun evalAnnotatedPrismElement(element: Element): AnnotatedOptic? = when {
element.let { it.kotlinMetadata as? KotlinClassMetadata }?.data?.classProto?.isSealed == true -> {
val (nameResolver, classProto) = element.kotlinMetadata.let { it as KotlinClassMetadata }.data

Expand All @@ -84,24 +86,24 @@ class OptikalProcessor : AbstractProcessor() {
)
}

else -> knownError(opticsAnnotationError(element, prismsAnnotationName, prismsAnnotationTarget))
else -> null
}

private fun opticsAnnotationError(element: Element, annotationName: String, targetName: String): String = """
|Cannot use $annotationName on ${element.enclosingElement}.${element.simpleName}.
|It can only be used on $targetName.""".trimMargin()

private fun evalAnnotatedIsoElement(element: Element): AnnotatedOptic = when {
private fun evalAnnotatedIsoElement(element: Element): AnnotatedOptic? = when {
(element.kotlinMetadata as? KotlinClassMetadata)?.data?.classProto?.isDataClass == true -> {
val properties = element.getConstructorTypesNames().zip(element.getConstructorParamNames(), ::Target)

if (properties.size > 10)
knownError("${element.enclosingElement}.${element.simpleName} up to 10 constructor parameters is supported")
else
if (properties.size > 10) {
logW("""
|Cannot generate arrow.optics.Iso for ${element.enclosingElement}.${element.simpleName}.
|Iso generation is supported up to 10 constructor parameters is supported
""")
null
} else
AnnotatedOptic(element as TypeElement, element.getClassData(), properties)
}

else -> knownError(opticsAnnotationError(element, isosAnnotationName, isosAnnotationTarget))
else -> null
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,13 @@ class OptionalFileGenerator(
private val generatedDir: File
) {

private val filePrefix = "optionals"
private val optional = "arrow.optics.Optional"

fun generate() = annotatedList.map(this::processElement)
.filter { it.second.joinToString(separator = "").isNotEmpty() }
.map { (element, funs) ->
"${optionalsAnnotationClass.simpleName}.${element.classData.`package`}.${element.type.simpleName.toString().toLowerCase()}.kt" to
"$filePrefix.${element.classData.`package`}.${element.type.simpleName.toString().toLowerCase()}.kt" to
funs.joinToString(prefix = fileHeader(element.classData.`package`.escapedClassName), separator = "\n")
}.forEach { (name, fileString) -> File(generatedDir, name).writeText(fileString) }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,12 @@ class PrismsFileGenerator(
private val generatedDir: File
) {

private val filePrefix = "prisms"
private val prism = "arrow.optics.Prism"

fun generate() = annotatedList.map(this::processElement)
.map { (element, funs) ->
"${prismsAnnotationClass.simpleName}.${element.classData.`package`}.${element.type.simpleName.toString().toLowerCase()}.kt" to
"$filePrefix.${element.classData.`package`}.${element.type.simpleName.toString().toLowerCase()}.kt" to
funs.joinToString(prefix = fileHeader(element.classData.`package`.escapedClassName), separator = "\n\n")
}.forEach { (name, fileString) -> File(generatedDir, name).writeText(fileString) }

Expand Down
16 changes: 4 additions & 12 deletions modules/core/arrow-annotations/src/main/java/arrow/optics.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,8 @@ import kotlin.annotation.AnnotationTarget.CLASS

@Retention(SOURCE)
@Target(CLASS)
annotation class lenses
annotation class optic(val targets: Array<OpticsTarget> = [OpticsTarget.ISO, OpticsTarget.LENS, OpticsTarget.PRISM, OpticsTarget.OPTIONAL, OpticsTarget.DSL])

@Retention(SOURCE)
@Target(CLASS)
annotation class prisms

@Retention(SOURCE)
@Target(CLASS)
annotation class isos

@Retention(SOURCE)
@Target(CLASS)
annotation class optionals
enum class OpticsTarget {
ISO, LENS, PRISM, OPTIONAL, DSL
}

0 comments on commit c59876d

Please sign in to comment.