mirror of
https://github.com/fankes/moshi.git
synced 2025-10-20 00:19:21 +08:00
Migrate to KotlinPoet-metadata (#903)
* Add kotlinpoet version to properties * Swap in kotlinpoet-metadata for kotlin-metadata in dependencies * Don't use KotlinMetadataUtils and KotlinAbstractProcessor anymore * Upcast to TypeElement * Temporarily add direct kotlinx-metadata dependency Something is wrong with packaging in my local kotlinpoet, will remove before * Remove tags API from TargetParameter No longer needed * Add PropertySpec directly to TargetProperty, remove holder funs Simplifies some things! * Move generated annotation gen into type callback Removes JsonClassCodegenProcessor completely from codegen API * Remove unstable autocommon dependency Won't be using this anymore after this change * Manually put quotes jsonName in AdapterGenerator Otherwise we could incur double escaping from kotlinpoet since we directly reuse possible-escaped parsed `@Json` name values * Opt in to use `@UseExperimental` annotation for preview * Rework and simplify metadata to just use kotlinpoet-metadata This is hard to do in broken down 1:1 steps, but the net result is a much smaller implementation footprint, better error messages, and a simpler API interaction with the code gen API. There is some raw parsing of kotlinpoet types required (mostly around annotations), but otherwise it's pretty smooth sailing and a good test of the upcoming kotlinpoet-metadata support * Declare KotlinPoetMetadataPreview directly * Try to point to snapshots for CI build * Update to classinspector API * Remove TypeResolver API No longer needed * Kotlinpoet 1.4.0 final * Fix missing import from rebase * Remove old kotlin-metadata version
This commit is contained in:
@@ -27,6 +27,26 @@
|
|||||||
<artifactId>kotlinpoet</artifactId>
|
<artifactId>kotlinpoet</artifactId>
|
||||||
<version>${kotlinpoet.version}</version>
|
<version>${kotlinpoet.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.jetbrains.kotlinx</groupId>
|
||||||
|
<artifactId>kotlinx-metadata-jvm</artifactId>
|
||||||
|
<version>0.1.0</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.squareup</groupId>
|
||||||
|
<artifactId>kotlinpoet-metadata</artifactId>
|
||||||
|
<version>${kotlinpoet.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.squareup</groupId>
|
||||||
|
<artifactId>kotlinpoet-metadata-specs</artifactId>
|
||||||
|
<version>${kotlinpoet.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.squareup</groupId>
|
||||||
|
<artifactId>kotlinpoet-classinspector-elements</artifactId>
|
||||||
|
<version>${kotlinpoet.version}</version>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>net.ltgt.gradle.incap</groupId>
|
<groupId>net.ltgt.gradle.incap</groupId>
|
||||||
<artifactId>incap</artifactId>
|
<artifactId>incap</artifactId>
|
||||||
@@ -34,11 +54,6 @@
|
|||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
<optional>true</optional>
|
<optional>true</optional>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
|
||||||
<groupId>com.google.auto</groupId>
|
|
||||||
<artifactId>auto-common</artifactId>
|
|
||||||
<version>${auto-common.version}</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.google.auto.service</groupId>
|
<groupId>com.google.auto.service</groupId>
|
||||||
<artifactId>auto-service-annotations</artifactId>
|
<artifactId>auto-service-annotations</artifactId>
|
||||||
@@ -67,10 +82,6 @@
|
|||||||
The Kotlin compiler usage must be near the end of the list because its .jar file includes an
|
The Kotlin compiler usage must be near the end of the list because its .jar file includes an
|
||||||
obsolete version of Guava!
|
obsolete version of Guava!
|
||||||
-->
|
-->
|
||||||
<dependency>
|
|
||||||
<groupId>me.eugeniomarletti.kotlin.metadata</groupId>
|
|
||||||
<artifactId>kotlin-metadata</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.github.tschuchortdev</groupId>
|
<groupId>com.github.tschuchortdev</groupId>
|
||||||
<artifactId>kotlin-compile-testing</artifactId>
|
<artifactId>kotlin-compile-testing</artifactId>
|
||||||
@@ -135,6 +146,12 @@
|
|||||||
</goals>
|
</goals>
|
||||||
</execution>
|
</execution>
|
||||||
</executions>
|
</executions>
|
||||||
|
<configuration>
|
||||||
|
<args>
|
||||||
|
<!-- So we can declare ourselves as users of KotlinPoetMetadataPreview -->
|
||||||
|
<arg>-Xuse-experimental=com.squareup.kotlinpoet.metadata.KotlinPoetMetadataPreview</arg>
|
||||||
|
</args>
|
||||||
|
</configuration>
|
||||||
</plugin>
|
</plugin>
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
@@ -15,10 +15,6 @@
|
|||||||
*/
|
*/
|
||||||
package com.squareup.moshi.kotlin.codegen
|
package com.squareup.moshi.kotlin.codegen
|
||||||
|
|
||||||
import com.squareup.kotlinpoet.TypeName
|
|
||||||
import com.squareup.kotlinpoet.TypeVariableName
|
|
||||||
import com.squareup.kotlinpoet.asTypeName
|
|
||||||
import com.squareup.moshi.kotlin.codegen.api.TypeResolver
|
|
||||||
import javax.lang.model.element.TypeElement
|
import javax.lang.model.element.TypeElement
|
||||||
import javax.lang.model.type.DeclaredType
|
import javax.lang.model.type.DeclaredType
|
||||||
import javax.lang.model.util.Types
|
import javax.lang.model.util.Types
|
||||||
@@ -29,7 +25,6 @@ import javax.lang.model.util.Types
|
|||||||
*/
|
*/
|
||||||
internal class AppliedType private constructor(
|
internal class AppliedType private constructor(
|
||||||
val element: TypeElement,
|
val element: TypeElement,
|
||||||
val resolver: TypeResolver,
|
|
||||||
private val mirror: DeclaredType
|
private val mirror: DeclaredType
|
||||||
) {
|
) {
|
||||||
/** Returns all supertypes of this, recursively. Includes both interface and class supertypes. */
|
/** Returns all supertypes of this, recursively. Includes both interface and class supertypes. */
|
||||||
@@ -41,32 +36,17 @@ internal class AppliedType private constructor(
|
|||||||
for (supertype in types.directSupertypes(mirror)) {
|
for (supertype in types.directSupertypes(mirror)) {
|
||||||
val supertypeDeclaredType = supertype as DeclaredType
|
val supertypeDeclaredType = supertype as DeclaredType
|
||||||
val supertypeElement = supertypeDeclaredType.asElement() as TypeElement
|
val supertypeElement = supertypeDeclaredType.asElement() as TypeElement
|
||||||
val appliedSupertype = AppliedType(supertypeElement,
|
val appliedSupertype = AppliedType(supertypeElement, supertypeDeclaredType)
|
||||||
resolver(supertypeElement, supertypeDeclaredType), supertypeDeclaredType)
|
|
||||||
appliedSupertype.supertypes(types, result)
|
appliedSupertype.supertypes(types, result)
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns a resolver that uses `element` and `mirror` to resolve type parameters. */
|
|
||||||
private fun resolver(element: TypeElement, mirror: DeclaredType): TypeResolver {
|
|
||||||
return object : TypeResolver() {
|
|
||||||
override fun resolveTypeVariable(typeVariable: TypeVariableName): TypeName {
|
|
||||||
val index = element.typeParameters.indexOfFirst {
|
|
||||||
it.simpleName.toString() == typeVariable.name
|
|
||||||
}
|
|
||||||
check(index != -1) { "Unexpected type variable $typeVariable in $mirror" }
|
|
||||||
val argument = mirror.typeArguments[index]
|
|
||||||
return argument.asTypeName()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun toString() = mirror.toString()
|
override fun toString() = mirror.toString()
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun get(typeElement: TypeElement): AppliedType {
|
fun get(typeElement: TypeElement): AppliedType {
|
||||||
return AppliedType(typeElement, TypeResolver(), typeElement.asType() as DeclaredType)
|
return AppliedType(typeElement, typeElement.asType() as DeclaredType)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -16,22 +16,25 @@
|
|||||||
package com.squareup.moshi.kotlin.codegen
|
package com.squareup.moshi.kotlin.codegen
|
||||||
|
|
||||||
import com.google.auto.service.AutoService
|
import com.google.auto.service.AutoService
|
||||||
|
import com.squareup.kotlinpoet.AnnotationSpec
|
||||||
import com.squareup.kotlinpoet.asClassName
|
import com.squareup.kotlinpoet.asClassName
|
||||||
|
import com.squareup.kotlinpoet.metadata.KotlinPoetMetadataPreview
|
||||||
import com.squareup.moshi.JsonClass
|
import com.squareup.moshi.JsonClass
|
||||||
import com.squareup.moshi.kotlin.codegen.api.AdapterGenerator
|
import com.squareup.moshi.kotlin.codegen.api.AdapterGenerator
|
||||||
import com.squareup.moshi.kotlin.codegen.api.PropertyGenerator
|
import com.squareup.moshi.kotlin.codegen.api.PropertyGenerator
|
||||||
import me.eugeniomarletti.kotlin.metadata.KotlinMetadataUtils
|
|
||||||
import me.eugeniomarletti.kotlin.processing.KotlinAbstractProcessor
|
|
||||||
import net.ltgt.gradle.incap.IncrementalAnnotationProcessor
|
import net.ltgt.gradle.incap.IncrementalAnnotationProcessor
|
||||||
import net.ltgt.gradle.incap.IncrementalAnnotationProcessorType.ISOLATING
|
import net.ltgt.gradle.incap.IncrementalAnnotationProcessorType.ISOLATING
|
||||||
|
import javax.annotation.processing.AbstractProcessor
|
||||||
|
import javax.annotation.processing.Filer
|
||||||
|
import javax.annotation.processing.Messager
|
||||||
import javax.annotation.processing.ProcessingEnvironment
|
import javax.annotation.processing.ProcessingEnvironment
|
||||||
import javax.annotation.processing.Processor
|
import javax.annotation.processing.Processor
|
||||||
import javax.annotation.processing.RoundEnvironment
|
import javax.annotation.processing.RoundEnvironment
|
||||||
import javax.lang.model.SourceVersion
|
import javax.lang.model.SourceVersion
|
||||||
import javax.lang.model.element.Element
|
|
||||||
import javax.lang.model.element.TypeElement
|
import javax.lang.model.element.TypeElement
|
||||||
import javax.lang.model.element.VariableElement
|
import javax.lang.model.util.Elements
|
||||||
import javax.tools.Diagnostic.Kind.ERROR
|
import javax.lang.model.util.Types
|
||||||
|
import javax.tools.Diagnostic
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An annotation processor that reads Kotlin data classes and generates Moshi JsonAdapters for them.
|
* An annotation processor that reads Kotlin data classes and generates Moshi JsonAdapters for them.
|
||||||
@@ -40,13 +43,11 @@ import javax.tools.Diagnostic.Kind.ERROR
|
|||||||
*
|
*
|
||||||
* The generated class will match the visibility of the given data class (i.e. if it's internal, the
|
* The generated class will match the visibility of the given data class (i.e. if it's internal, the
|
||||||
* adapter will also be internal).
|
* adapter will also be internal).
|
||||||
*
|
|
||||||
* If you define a companion object, a jsonAdapter() extension function will be generated onto it.
|
|
||||||
* If you don't want this though, you can use the runtime [JsonClass] factory implementation.
|
|
||||||
*/
|
*/
|
||||||
|
@KotlinPoetMetadataPreview
|
||||||
@AutoService(Processor::class)
|
@AutoService(Processor::class)
|
||||||
@IncrementalAnnotationProcessor(ISOLATING)
|
@IncrementalAnnotationProcessor(ISOLATING)
|
||||||
class JsonClassCodegenProcessor : KotlinAbstractProcessor(), KotlinMetadataUtils {
|
class JsonClassCodegenProcessor : AbstractProcessor() {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
/**
|
/**
|
||||||
@@ -65,6 +66,10 @@ class JsonClassCodegenProcessor : KotlinAbstractProcessor(), KotlinMetadataUtils
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private lateinit var types: Types
|
||||||
|
private lateinit var elements: Elements
|
||||||
|
private lateinit var filer: Filer
|
||||||
|
private lateinit var messager: Messager
|
||||||
private val annotation = JsonClass::class.java
|
private val annotation = JsonClass::class.java
|
||||||
private var generatedType: TypeElement? = null
|
private var generatedType: TypeElement? = null
|
||||||
|
|
||||||
@@ -83,16 +88,37 @@ class JsonClassCodegenProcessor : KotlinAbstractProcessor(), KotlinMetadataUtils
|
|||||||
}
|
}
|
||||||
processingEnv.elementUtils.getTypeElement(it)
|
processingEnv.elementUtils.getTypeElement(it)
|
||||||
}
|
}
|
||||||
|
this.types = processingEnv.typeUtils
|
||||||
|
this.elements = processingEnv.elementUtils
|
||||||
|
this.filer = processingEnv.filer
|
||||||
|
this.messager = processingEnv.messager
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun process(annotations: Set<TypeElement>, roundEnv: RoundEnvironment): Boolean {
|
override fun process(annotations: Set<TypeElement>, roundEnv: RoundEnvironment): Boolean {
|
||||||
for (type in roundEnv.getElementsAnnotatedWith(annotation)) {
|
for (type in roundEnv.getElementsAnnotatedWith(annotation)) {
|
||||||
|
if (type !is TypeElement) {
|
||||||
|
messager.printMessage(
|
||||||
|
Diagnostic.Kind.ERROR, "@JsonClass can't be applied to $type: must be a Kotlin class",
|
||||||
|
type)
|
||||||
|
continue
|
||||||
|
}
|
||||||
val jsonClass = type.getAnnotation(annotation)
|
val jsonClass = type.getAnnotation(annotation)
|
||||||
if (jsonClass.generateAdapter && jsonClass.generator.isEmpty()) {
|
if (jsonClass.generateAdapter && jsonClass.generator.isEmpty()) {
|
||||||
val generator = adapterGenerator(type) ?: continue
|
val generator = adapterGenerator(type) ?: continue
|
||||||
generator
|
generator
|
||||||
.generateFile(generatedType?.asClassName()) {
|
.generateFile {
|
||||||
it.toBuilder()
|
it.toBuilder()
|
||||||
|
.apply {
|
||||||
|
generatedType?.asClassName()?.let { generatedClassName ->
|
||||||
|
addAnnotation(
|
||||||
|
AnnotationSpec.builder(generatedClassName)
|
||||||
|
.addMember("value = [%S]",
|
||||||
|
JsonClassCodegenProcessor::class.java.canonicalName)
|
||||||
|
.addMember("comments = %S", "https://github.com/square/moshi")
|
||||||
|
.build()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
.addOriginatingElement(type)
|
.addOriginatingElement(type)
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
@@ -103,12 +129,12 @@ class JsonClassCodegenProcessor : KotlinAbstractProcessor(), KotlinMetadataUtils
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun adapterGenerator(element: Element): AdapterGenerator? {
|
private fun adapterGenerator(element: TypeElement): AdapterGenerator? {
|
||||||
val type = targetType(messager, elementUtils, typeUtils, element) ?: return null
|
val type = targetType(messager, elements, types, element) ?: return null
|
||||||
|
|
||||||
val properties = mutableMapOf<String, PropertyGenerator>()
|
val properties = mutableMapOf<String, PropertyGenerator>()
|
||||||
for (property in type.properties.values) {
|
for (property in type.properties.values) {
|
||||||
val generator = property.generator(messager)
|
val generator = property.generator(messager, element, elements)
|
||||||
if (generator != null) {
|
if (generator != null) {
|
||||||
properties[property.name] = generator
|
properties[property.name] = generator
|
||||||
}
|
}
|
||||||
@@ -117,7 +143,9 @@ class JsonClassCodegenProcessor : KotlinAbstractProcessor(), KotlinMetadataUtils
|
|||||||
for ((name, parameter) in type.constructor.parameters) {
|
for ((name, parameter) in type.constructor.parameters) {
|
||||||
if (type.properties[parameter.name] == null && !parameter.hasDefault) {
|
if (type.properties[parameter.name] == null && !parameter.hasDefault) {
|
||||||
messager.printMessage(
|
messager.printMessage(
|
||||||
ERROR, "No property for required constructor parameter $name", parameter.tag<VariableElement>())
|
Diagnostic.Kind.ERROR,
|
||||||
|
"No property for required constructor parameter $name",
|
||||||
|
element)
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -17,7 +17,6 @@ package com.squareup.moshi.kotlin.codegen.api
|
|||||||
|
|
||||||
import com.squareup.kotlinpoet.ARRAY
|
import com.squareup.kotlinpoet.ARRAY
|
||||||
import com.squareup.kotlinpoet.AnnotationSpec
|
import com.squareup.kotlinpoet.AnnotationSpec
|
||||||
import com.squareup.kotlinpoet.ClassName
|
|
||||||
import com.squareup.kotlinpoet.CodeBlock
|
import com.squareup.kotlinpoet.CodeBlock
|
||||||
import com.squareup.kotlinpoet.FileSpec
|
import com.squareup.kotlinpoet.FileSpec
|
||||||
import com.squareup.kotlinpoet.FunSpec
|
import com.squareup.kotlinpoet.FunSpec
|
||||||
@@ -36,7 +35,6 @@ import com.squareup.moshi.JsonReader
|
|||||||
import com.squareup.moshi.JsonWriter
|
import com.squareup.moshi.JsonWriter
|
||||||
import com.squareup.moshi.Moshi
|
import com.squareup.moshi.Moshi
|
||||||
import com.squareup.moshi.internal.Util
|
import com.squareup.moshi.internal.Util
|
||||||
import com.squareup.moshi.kotlin.codegen.JsonClassCodegenProcessor
|
|
||||||
import java.lang.reflect.Constructor
|
import java.lang.reflect.Constructor
|
||||||
import java.lang.reflect.Type
|
import java.lang.reflect.Type
|
||||||
|
|
||||||
@@ -83,7 +81,8 @@ internal class AdapterGenerator(
|
|||||||
nameAllocator.newName("options"), JsonReader.Options::class.asTypeName(),
|
nameAllocator.newName("options"), JsonReader.Options::class.asTypeName(),
|
||||||
KModifier.PRIVATE)
|
KModifier.PRIVATE)
|
||||||
.initializer("%T.of(${nonTransientProperties.joinToString(", ") {
|
.initializer("%T.of(${nonTransientProperties.joinToString(", ") {
|
||||||
CodeBlock.of("%S", it.jsonName).toString()
|
// We manually put in quotes because we know the jsonName is already escaped
|
||||||
|
CodeBlock.of("\"%L\"", it.jsonName).toString()
|
||||||
}})", JsonReader.Options::class.asTypeName())
|
}})", JsonReader.Options::class.asTypeName())
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
@@ -96,27 +95,20 @@ internal class AdapterGenerator(
|
|||||||
.initializer("null")
|
.initializer("null")
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
fun generateFile(generatedOption: ClassName?, typeHook: (TypeSpec) -> TypeSpec = { it }): FileSpec {
|
fun generateFile(typeHook: (TypeSpec) -> TypeSpec = { it }): FileSpec {
|
||||||
for (property in nonTransientProperties) {
|
for (property in nonTransientProperties) {
|
||||||
property.allocateNames(nameAllocator)
|
property.allocateNames(nameAllocator)
|
||||||
}
|
}
|
||||||
|
|
||||||
val result = FileSpec.builder(className.packageName, adapterName)
|
val result = FileSpec.builder(className.packageName, adapterName)
|
||||||
result.addComment("Code generated by moshi-kotlin-codegen. Do not edit.")
|
result.addComment("Code generated by moshi-kotlin-codegen. Do not edit.")
|
||||||
result.addType(generateType(generatedOption).let(typeHook))
|
result.addType(generateType().let(typeHook))
|
||||||
return result.build()
|
return result.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun generateType(generatedOption: ClassName?): TypeSpec {
|
private fun generateType(): TypeSpec {
|
||||||
val result = TypeSpec.classBuilder(adapterName)
|
val result = TypeSpec.classBuilder(adapterName)
|
||||||
|
|
||||||
generatedOption?.let {
|
|
||||||
result.addAnnotation(AnnotationSpec.builder(it)
|
|
||||||
.addMember("value = [%S]", JsonClassCodegenProcessor::class.java.canonicalName)
|
|
||||||
.addMember("comments = %S", "https://github.com/square/moshi")
|
|
||||||
.build())
|
|
||||||
}
|
|
||||||
|
|
||||||
result.superclass(jsonAdapterTypeName)
|
result.superclass(jsonAdapterTypeName)
|
||||||
|
|
||||||
if (typeVariables.isNotEmpty()) {
|
if (typeVariables.isNotEmpty()) {
|
||||||
@@ -366,7 +358,8 @@ internal class AdapterGenerator(
|
|||||||
|
|
||||||
result.addStatement("%N.beginObject()", writerParam)
|
result.addStatement("%N.beginObject()", writerParam)
|
||||||
nonTransientProperties.forEach { property ->
|
nonTransientProperties.forEach { property ->
|
||||||
result.addStatement("%N.name(%S)", writerParam, property.jsonName)
|
// We manually put in quotes because we know the jsonName is already escaped
|
||||||
|
result.addStatement("%N.name(\"%L\")", writerParam, property.jsonName)
|
||||||
result.addStatement("%N.toJson(%N, %N.%N)",
|
result.addStatement("%N.toJson(%N, %N.%N)",
|
||||||
nameAllocator[property.delegateKey], writerParam, valueParam, property.name)
|
nameAllocator[property.delegateKey], writerParam, valueParam, property.name)
|
||||||
}
|
}
|
||||||
|
@@ -16,7 +16,6 @@
|
|||||||
package com.squareup.moshi.kotlin.codegen.api
|
package com.squareup.moshi.kotlin.codegen.api
|
||||||
|
|
||||||
import com.squareup.kotlinpoet.AnnotationSpec
|
import com.squareup.kotlinpoet.AnnotationSpec
|
||||||
import kotlin.reflect.KClass
|
|
||||||
|
|
||||||
/** A parameter in user code that should be populated by generated code. */
|
/** A parameter in user code that should be populated by generated code. */
|
||||||
internal data class TargetParameter(
|
internal data class TargetParameter(
|
||||||
@@ -24,15 +23,5 @@ internal data class TargetParameter(
|
|||||||
val index: Int,
|
val index: Int,
|
||||||
val hasDefault: Boolean,
|
val hasDefault: Boolean,
|
||||||
val jsonName: String = name,
|
val jsonName: String = name,
|
||||||
val qualifiers: Set<AnnotationSpec>? = null,
|
val qualifiers: Set<AnnotationSpec>? = null
|
||||||
private val tags: Map<KClass<*>, Any>
|
)
|
||||||
) {
|
|
||||||
/** Returns the tag attached with [type] as a key, or null if no tag is attached with that key. */
|
|
||||||
fun <T : Any> tag(type: KClass<out T>): T? {
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
|
||||||
return tags[type] as T?
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Returns the tag attached with [T] as a key, or null if no tag is attached with that key. */
|
|
||||||
inline fun <reified T : Any> tag(): T? = tag(T::class)
|
|
||||||
}
|
|
||||||
|
@@ -15,23 +15,19 @@
|
|||||||
*/
|
*/
|
||||||
package com.squareup.moshi.kotlin.codegen.api
|
package com.squareup.moshi.kotlin.codegen.api
|
||||||
|
|
||||||
import com.squareup.kotlinpoet.FunSpec
|
|
||||||
import com.squareup.kotlinpoet.KModifier
|
import com.squareup.kotlinpoet.KModifier
|
||||||
import com.squareup.kotlinpoet.PropertySpec
|
import com.squareup.kotlinpoet.PropertySpec
|
||||||
import com.squareup.kotlinpoet.TypeName
|
import com.squareup.kotlinpoet.TypeName
|
||||||
|
|
||||||
/** A property in user code that maps to JSON. */
|
/** A property in user code that maps to JSON. */
|
||||||
internal data class TargetProperty(
|
internal data class TargetProperty(
|
||||||
val name: String,
|
val propertySpec: PropertySpec,
|
||||||
val type: TypeName,
|
|
||||||
val parameter: TargetParameter?,
|
val parameter: TargetParameter?,
|
||||||
val annotationHolder: FunSpec?,
|
|
||||||
val field: PropertySpec?,
|
|
||||||
val setter: FunSpec?,
|
|
||||||
val getter: FunSpec?,
|
|
||||||
val visibility: KModifier,
|
val visibility: KModifier,
|
||||||
val jsonName: String
|
val jsonName: String
|
||||||
) {
|
) {
|
||||||
|
val name: String get() = propertySpec.name
|
||||||
|
val type: TypeName get() = propertySpec.type
|
||||||
val parameterIndex get() = parameter?.index ?: -1
|
val parameterIndex get() = parameter?.index ?: -1
|
||||||
val hasDefault get() = parameter?.hasDefault ?: true
|
val hasDefault get() = parameter?.hasDefault ?: true
|
||||||
|
|
||||||
|
@@ -1,63 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2018 Square, Inc.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package com.squareup.moshi.kotlin.codegen.api
|
|
||||||
|
|
||||||
import com.squareup.kotlinpoet.ClassName
|
|
||||||
import com.squareup.kotlinpoet.ParameterizedTypeName
|
|
||||||
import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
|
|
||||||
import com.squareup.kotlinpoet.TypeName
|
|
||||||
import com.squareup.kotlinpoet.TypeVariableName
|
|
||||||
import com.squareup.kotlinpoet.WildcardTypeName
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resolves type parameters against a type declaration. Use this to fill in type variables with
|
|
||||||
* their actual type parameters.
|
|
||||||
*/
|
|
||||||
open class TypeResolver {
|
|
||||||
open fun resolveTypeVariable(typeVariable: TypeVariableName): TypeName = typeVariable
|
|
||||||
|
|
||||||
fun resolve(typeName: TypeName): TypeName {
|
|
||||||
return when (typeName) {
|
|
||||||
is ClassName -> typeName
|
|
||||||
|
|
||||||
is ParameterizedTypeName -> {
|
|
||||||
typeName.rawType.parameterizedBy(*(typeName.typeArguments.map { resolve(it) }.toTypedArray()))
|
|
||||||
.copy(nullable = typeName.isNullable)
|
|
||||||
}
|
|
||||||
|
|
||||||
is WildcardTypeName -> {
|
|
||||||
when {
|
|
||||||
typeName.inTypes.size == 1 -> {
|
|
||||||
WildcardTypeName.consumerOf(resolve(typeName.inTypes[0]))
|
|
||||||
.copy(nullable = typeName.isNullable)
|
|
||||||
}
|
|
||||||
typeName.outTypes.size == 1 -> {
|
|
||||||
WildcardTypeName.producerOf(resolve(typeName.outTypes[0]))
|
|
||||||
.copy(nullable = typeName.isNullable)
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
throw IllegalArgumentException(
|
|
||||||
"Unrepresentable wildcard type. Cannot have more than one bound: $typeName")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
is TypeVariableName -> resolveTypeVariable(typeName)
|
|
||||||
|
|
||||||
else -> throw IllegalArgumentException("Unrepresentable type: $typeName")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -15,26 +15,23 @@
|
|||||||
*/
|
*/
|
||||||
package com.squareup.moshi.kotlin.codegen
|
package com.squareup.moshi.kotlin.codegen
|
||||||
|
|
||||||
import com.google.auto.common.AnnotationMirrors
|
|
||||||
import com.google.auto.common.MoreTypes
|
|
||||||
import com.squareup.kotlinpoet.AnnotationSpec
|
import com.squareup.kotlinpoet.AnnotationSpec
|
||||||
import com.squareup.kotlinpoet.ClassName
|
import com.squareup.kotlinpoet.ClassName
|
||||||
import com.squareup.kotlinpoet.CodeBlock
|
|
||||||
import com.squareup.kotlinpoet.FunSpec
|
|
||||||
import com.squareup.kotlinpoet.KModifier
|
import com.squareup.kotlinpoet.KModifier
|
||||||
import com.squareup.kotlinpoet.KModifier.PUBLIC
|
import com.squareup.kotlinpoet.TypeSpec
|
||||||
import com.squareup.kotlinpoet.KModifier.VARARG
|
|
||||||
import com.squareup.kotlinpoet.ParameterSpec
|
|
||||||
import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
|
|
||||||
import com.squareup.kotlinpoet.PropertySpec
|
|
||||||
import com.squareup.kotlinpoet.STAR
|
|
||||||
import com.squareup.kotlinpoet.TypeName
|
|
||||||
import com.squareup.kotlinpoet.TypeVariableName
|
|
||||||
import com.squareup.kotlinpoet.WildcardTypeName
|
|
||||||
import com.squareup.kotlinpoet.asClassName
|
import com.squareup.kotlinpoet.asClassName
|
||||||
import com.squareup.kotlinpoet.asTypeName
|
import com.squareup.kotlinpoet.asTypeName
|
||||||
import com.squareup.kotlinpoet.asTypeVariableName
|
import com.squareup.kotlinpoet.classinspector.elements.ElementsClassInspector
|
||||||
import com.squareup.kotlinpoet.tag
|
import com.squareup.kotlinpoet.metadata.KotlinPoetMetadataPreview
|
||||||
|
import com.squareup.kotlinpoet.metadata.isAbstract
|
||||||
|
import com.squareup.kotlinpoet.metadata.isClass
|
||||||
|
import com.squareup.kotlinpoet.metadata.isEnum
|
||||||
|
import com.squareup.kotlinpoet.metadata.isInner
|
||||||
|
import com.squareup.kotlinpoet.metadata.isLocal
|
||||||
|
import com.squareup.kotlinpoet.metadata.isSealed
|
||||||
|
import com.squareup.kotlinpoet.metadata.specs.ClassInspector
|
||||||
|
import com.squareup.kotlinpoet.metadata.specs.toTypeSpec
|
||||||
|
import com.squareup.kotlinpoet.metadata.toImmutableKmClass
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
import com.squareup.moshi.JsonQualifier
|
import com.squareup.moshi.JsonQualifier
|
||||||
import com.squareup.moshi.kotlin.codegen.api.DelegateKey
|
import com.squareup.moshi.kotlin.codegen.api.DelegateKey
|
||||||
@@ -43,264 +40,125 @@ import com.squareup.moshi.kotlin.codegen.api.TargetConstructor
|
|||||||
import com.squareup.moshi.kotlin.codegen.api.TargetParameter
|
import com.squareup.moshi.kotlin.codegen.api.TargetParameter
|
||||||
import com.squareup.moshi.kotlin.codegen.api.TargetProperty
|
import com.squareup.moshi.kotlin.codegen.api.TargetProperty
|
||||||
import com.squareup.moshi.kotlin.codegen.api.TargetType
|
import com.squareup.moshi.kotlin.codegen.api.TargetType
|
||||||
import com.squareup.moshi.kotlin.codegen.api.TypeResolver
|
|
||||||
import me.eugeniomarletti.kotlin.metadata.KotlinClassMetadata
|
|
||||||
import me.eugeniomarletti.kotlin.metadata.KotlinMetadata
|
|
||||||
import me.eugeniomarletti.kotlin.metadata.classKind
|
|
||||||
import me.eugeniomarletti.kotlin.metadata.declaresDefaultValue
|
|
||||||
import me.eugeniomarletti.kotlin.metadata.getPropertyOrNull
|
|
||||||
import me.eugeniomarletti.kotlin.metadata.isDataClass
|
|
||||||
import me.eugeniomarletti.kotlin.metadata.isInnerClass
|
|
||||||
import me.eugeniomarletti.kotlin.metadata.isPrimary
|
|
||||||
import me.eugeniomarletti.kotlin.metadata.jvm.getJvmConstructorSignature
|
|
||||||
import me.eugeniomarletti.kotlin.metadata.kotlinMetadata
|
|
||||||
import me.eugeniomarletti.kotlin.metadata.modality
|
|
||||||
import me.eugeniomarletti.kotlin.metadata.shadow.metadata.ProtoBuf.Class
|
|
||||||
import me.eugeniomarletti.kotlin.metadata.shadow.metadata.ProtoBuf.Modality.ABSTRACT
|
|
||||||
import me.eugeniomarletti.kotlin.metadata.shadow.metadata.ProtoBuf.Modality.SEALED
|
|
||||||
import me.eugeniomarletti.kotlin.metadata.shadow.metadata.ProtoBuf.Type
|
|
||||||
import me.eugeniomarletti.kotlin.metadata.shadow.metadata.ProtoBuf.TypeParameter
|
|
||||||
import me.eugeniomarletti.kotlin.metadata.shadow.metadata.ProtoBuf.TypeParameter.Variance
|
|
||||||
import me.eugeniomarletti.kotlin.metadata.shadow.metadata.ProtoBuf.Visibility
|
|
||||||
import me.eugeniomarletti.kotlin.metadata.shadow.metadata.ProtoBuf.Visibility.INTERNAL
|
|
||||||
import me.eugeniomarletti.kotlin.metadata.shadow.metadata.ProtoBuf.Visibility.LOCAL
|
|
||||||
import me.eugeniomarletti.kotlin.metadata.shadow.metadata.ProtoBuf.Visibility.PRIVATE
|
|
||||||
import me.eugeniomarletti.kotlin.metadata.shadow.metadata.ProtoBuf.Visibility.PRIVATE_TO_THIS
|
|
||||||
import me.eugeniomarletti.kotlin.metadata.shadow.metadata.ProtoBuf.Visibility.PROTECTED
|
|
||||||
import me.eugeniomarletti.kotlin.metadata.shadow.metadata.deserialization.NameResolver
|
|
||||||
import me.eugeniomarletti.kotlin.metadata.shadow.util.capitalizeDecapitalize.decapitalizeAsciiOnly
|
|
||||||
import me.eugeniomarletti.kotlin.metadata.visibility
|
|
||||||
import java.lang.annotation.ElementType
|
import java.lang.annotation.ElementType
|
||||||
import java.lang.annotation.Retention
|
import java.lang.annotation.Retention
|
||||||
import java.lang.annotation.RetentionPolicy
|
import java.lang.annotation.RetentionPolicy
|
||||||
import java.lang.annotation.Target
|
import java.lang.annotation.Target
|
||||||
import javax.annotation.processing.Messager
|
import javax.annotation.processing.Messager
|
||||||
import javax.lang.model.element.AnnotationMirror
|
|
||||||
import javax.lang.model.element.Element
|
|
||||||
import javax.lang.model.element.ElementKind
|
import javax.lang.model.element.ElementKind
|
||||||
import javax.lang.model.element.ExecutableElement
|
|
||||||
import javax.lang.model.element.Modifier
|
|
||||||
import javax.lang.model.element.Modifier.DEFAULT
|
|
||||||
import javax.lang.model.element.Modifier.FINAL
|
|
||||||
import javax.lang.model.element.Modifier.NATIVE
|
|
||||||
import javax.lang.model.element.Modifier.STATIC
|
|
||||||
import javax.lang.model.element.Modifier.SYNCHRONIZED
|
|
||||||
import javax.lang.model.element.Modifier.TRANSIENT
|
|
||||||
import javax.lang.model.element.Modifier.VOLATILE
|
|
||||||
import javax.lang.model.element.TypeElement
|
import javax.lang.model.element.TypeElement
|
||||||
import javax.lang.model.element.VariableElement
|
|
||||||
import javax.lang.model.type.TypeVariable
|
|
||||||
import javax.lang.model.util.Elements
|
import javax.lang.model.util.Elements
|
||||||
import javax.lang.model.util.Types
|
import javax.lang.model.util.Types
|
||||||
import javax.tools.Diagnostic
|
import javax.tools.Diagnostic
|
||||||
import javax.tools.Diagnostic.Kind.ERROR
|
|
||||||
import kotlin.reflect.KClass
|
|
||||||
|
|
||||||
private fun TypeParameter.asTypeName(
|
private val JSON_QUALIFIER = JsonQualifier::class.java
|
||||||
nameResolver: NameResolver,
|
private val JSON = Json::class.asClassName()
|
||||||
getTypeParameter: (index: Int) -> TypeParameter,
|
private val OBJECT_CLASS = ClassName("java.lang", "Object")
|
||||||
resolveAliases: Boolean = false
|
private val VISIBILITY_MODIFIERS = setOf(
|
||||||
): TypeVariableName {
|
KModifier.INTERNAL,
|
||||||
val possibleBounds = upperBoundList.map {
|
KModifier.PRIVATE,
|
||||||
it.asTypeName(nameResolver, getTypeParameter, resolveAliases)
|
KModifier.PROTECTED,
|
||||||
}
|
KModifier.PUBLIC
|
||||||
return if (possibleBounds.isEmpty()) {
|
)
|
||||||
TypeVariableName(
|
|
||||||
name = nameResolver.getString(name),
|
private fun Collection<KModifier>.visibility(): KModifier {
|
||||||
variance = variance.asKModifier())
|
return find { it in VISIBILITY_MODIFIERS } ?: KModifier.PUBLIC
|
||||||
} else {
|
|
||||||
TypeVariableName(
|
|
||||||
name = nameResolver.getString(name),
|
|
||||||
bounds = *possibleBounds.toTypedArray(),
|
|
||||||
variance = variance.asKModifier())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun TypeParameter.Variance.asKModifier(): KModifier? {
|
@KotlinPoetMetadataPreview
|
||||||
return when (this) {
|
internal fun primaryConstructor(kotlinApi: TypeSpec, elements: Elements): TargetConstructor? {
|
||||||
Variance.IN -> KModifier.IN
|
val primaryConstructor = kotlinApi.primaryConstructor ?: return null
|
||||||
Variance.OUT -> KModifier.OUT
|
|
||||||
Variance.INV -> null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun Visibility?.asKModifier(): KModifier {
|
|
||||||
return when (this) {
|
|
||||||
INTERNAL -> KModifier.INTERNAL
|
|
||||||
PRIVATE -> KModifier.PRIVATE
|
|
||||||
PROTECTED -> KModifier.PROTECTED
|
|
||||||
Visibility.PUBLIC -> KModifier.PUBLIC
|
|
||||||
PRIVATE_TO_THIS -> KModifier.PRIVATE
|
|
||||||
LOCAL -> KModifier.PRIVATE
|
|
||||||
else -> PUBLIC
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the TypeName of this type as it would be seen in the source code, including nullability
|
|
||||||
* and generic type parameters.
|
|
||||||
*
|
|
||||||
* @param [nameResolver] a [NameResolver] instance from the source proto
|
|
||||||
* @param [getTypeParameter] a function that returns the type parameter for the given index. **Only
|
|
||||||
* called if [ProtoBuf.Type.hasTypeParameter] is true!**
|
|
||||||
*/
|
|
||||||
private fun Type.asTypeName(
|
|
||||||
nameResolver: NameResolver,
|
|
||||||
getTypeParameter: (index: Int) -> TypeParameter,
|
|
||||||
useAbbreviatedType: Boolean = true
|
|
||||||
): TypeName {
|
|
||||||
|
|
||||||
val argumentList = when {
|
|
||||||
useAbbreviatedType && hasAbbreviatedType() -> abbreviatedType.argumentList
|
|
||||||
else -> argumentList
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasFlexibleUpperBound()) {
|
|
||||||
return WildcardTypeName.producerOf(
|
|
||||||
flexibleUpperBound.asTypeName(nameResolver, getTypeParameter, useAbbreviatedType))
|
|
||||||
.copy(nullable = nullable)
|
|
||||||
} else if (hasOuterType()) {
|
|
||||||
return WildcardTypeName.consumerOf(
|
|
||||||
outerType.asTypeName(nameResolver, getTypeParameter, useAbbreviatedType))
|
|
||||||
.copy(nullable = nullable)
|
|
||||||
}
|
|
||||||
|
|
||||||
val realType = when {
|
|
||||||
hasTypeParameter() -> return getTypeParameter(typeParameter)
|
|
||||||
.asTypeName(nameResolver, getTypeParameter, useAbbreviatedType)
|
|
||||||
.copy(nullable = nullable)
|
|
||||||
hasTypeParameterName() -> typeParameterName
|
|
||||||
useAbbreviatedType && hasAbbreviatedType() -> abbreviatedType.typeAliasName
|
|
||||||
else -> className
|
|
||||||
}
|
|
||||||
|
|
||||||
var typeName: TypeName =
|
|
||||||
ClassName.bestGuess(nameResolver.getString(realType)
|
|
||||||
.replace("/", "."))
|
|
||||||
|
|
||||||
if (argumentList.isNotEmpty()) {
|
|
||||||
val remappedArgs: Array<TypeName> = argumentList.map { argumentType ->
|
|
||||||
val nullableProjection = if (argumentType.hasProjection()) {
|
|
||||||
argumentType.projection
|
|
||||||
} else null
|
|
||||||
if (argumentType.hasType()) {
|
|
||||||
argumentType.type.asTypeName(nameResolver, getTypeParameter, useAbbreviatedType)
|
|
||||||
.let { argumentTypeName ->
|
|
||||||
nullableProjection?.let { projection ->
|
|
||||||
when (projection) {
|
|
||||||
Type.Argument.Projection.IN -> WildcardTypeName.consumerOf(argumentTypeName)
|
|
||||||
Type.Argument.Projection.OUT -> WildcardTypeName.producerOf(argumentTypeName)
|
|
||||||
Type.Argument.Projection.STAR -> STAR
|
|
||||||
Type.Argument.Projection.INV -> TODO("INV projection is unsupported")
|
|
||||||
}
|
|
||||||
} ?: argumentTypeName
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
STAR
|
|
||||||
}
|
|
||||||
}.toTypedArray()
|
|
||||||
typeName = (typeName as ClassName).parameterizedBy(*remappedArgs)
|
|
||||||
}
|
|
||||||
|
|
||||||
return typeName.copy(nullable = nullable)
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun primaryConstructor(metadata: KotlinClassMetadata,
|
|
||||||
elements: Elements): TargetConstructor {
|
|
||||||
val (nameResolver, classProto) = metadata.data
|
|
||||||
|
|
||||||
// TODO allow custom constructor
|
|
||||||
val proto = classProto.constructorList
|
|
||||||
.single { it.isPrimary }
|
|
||||||
val constructorJvmSignature = proto.getJvmConstructorSignature(
|
|
||||||
nameResolver, classProto.typeTable)
|
|
||||||
val element = classProto.fqName
|
|
||||||
.let(nameResolver::getString)
|
|
||||||
.replace('/', '.')
|
|
||||||
.let(elements::getTypeElement)
|
|
||||||
.enclosedElements
|
|
||||||
.mapNotNull {
|
|
||||||
it.takeIf { it.kind == ElementKind.CONSTRUCTOR }?.let { it as ExecutableElement }
|
|
||||||
}
|
|
||||||
.first()
|
|
||||||
// TODO Temporary until JVM method signature matching is better
|
|
||||||
// .single { it.jvmMethodSignature == constructorJvmSignature }
|
|
||||||
|
|
||||||
val parameters = mutableMapOf<String, TargetParameter>()
|
val parameters = mutableMapOf<String, TargetParameter>()
|
||||||
for (parameter in proto.valueParameterList) {
|
for ((index, parameter) in primaryConstructor.parameters.withIndex()) {
|
||||||
val name = nameResolver.getString(parameter.name)
|
val name = parameter.name
|
||||||
val index = proto.valueParameterList.indexOf(parameter)
|
|
||||||
val paramElement = element.parameters[index]
|
|
||||||
parameters[name] = TargetParameter(
|
parameters[name] = TargetParameter(
|
||||||
name = name,
|
name = name,
|
||||||
index = index,
|
index = index,
|
||||||
hasDefault = parameter.declaresDefaultValue,
|
hasDefault = parameter.defaultValue != null,
|
||||||
qualifiers = paramElement.qualifiers.mapTo(mutableSetOf(), AnnotationMirror::asAnnotationSpec),
|
qualifiers = parameter.annotations.qualifiers(elements),
|
||||||
jsonName = paramElement.jsonName ?: name,
|
jsonName = parameter.annotations.jsonName() ?: name.escapeDollarSigns()
|
||||||
tags = mapOf(VariableElement::class to paramElement)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return TargetConstructor(parameters,
|
return TargetConstructor(parameters, primaryConstructor.modifiers.visibility())
|
||||||
proto.visibility.asKModifier())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private val OBJECT_CLASS = ClassName("java.lang", "Object")
|
|
||||||
|
|
||||||
/** Returns a target type for `element`, or null if it cannot be used with code gen. */
|
/** Returns a target type for `element`, or null if it cannot be used with code gen. */
|
||||||
|
@KotlinPoetMetadataPreview
|
||||||
internal fun targetType(messager: Messager,
|
internal fun targetType(messager: Messager,
|
||||||
elements: Elements,
|
elements: Elements,
|
||||||
types: Types,
|
types: Types,
|
||||||
element: Element): TargetType? {
|
element: TypeElement): TargetType? {
|
||||||
val typeMetadata: KotlinMetadata? = element.kotlinMetadata
|
val typeMetadata = element.getAnnotation(Metadata::class.java)
|
||||||
if (element !is TypeElement || typeMetadata !is KotlinClassMetadata) {
|
if (typeMetadata == null) {
|
||||||
messager.printMessage(
|
messager.printMessage(
|
||||||
ERROR, "@JsonClass can't be applied to $element: must be a Kotlin class", element)
|
Diagnostic.Kind.ERROR, "@JsonClass can't be applied to $element: must be a Kotlin class",
|
||||||
|
element)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
val kmClass = try {
|
||||||
|
typeMetadata.toImmutableKmClass()
|
||||||
|
} catch (e: UnsupportedOperationException) {
|
||||||
|
messager.printMessage(
|
||||||
|
Diagnostic.Kind.ERROR, "@JsonClass can't be applied to $element: must be a Class type",
|
||||||
|
element)
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
val proto = typeMetadata.data.classProto
|
|
||||||
when {
|
when {
|
||||||
proto.classKind == Class.Kind.ENUM_CLASS -> {
|
kmClass.isEnum -> {
|
||||||
messager.printMessage(
|
messager.printMessage(
|
||||||
ERROR,
|
Diagnostic.Kind.ERROR,
|
||||||
"@JsonClass with 'generateAdapter = \"true\"' can't be applied to $element: code gen for enums is not supported or necessary",
|
"@JsonClass with 'generateAdapter = \"true\"' can't be applied to $element: code gen for enums is not supported or necessary",
|
||||||
element)
|
element)
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
proto.classKind != Class.Kind.CLASS -> {
|
!kmClass.isClass -> {
|
||||||
messager.printMessage(
|
messager.printMessage(
|
||||||
ERROR, "@JsonClass can't be applied to $element: must be a Kotlin class", element)
|
Diagnostic.Kind.ERROR, "@JsonClass can't be applied to $element: must be a Kotlin class",
|
||||||
|
element)
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
proto.isInnerClass -> {
|
kmClass.isInner -> {
|
||||||
messager.printMessage(
|
messager.printMessage(
|
||||||
ERROR, "@JsonClass can't be applied to $element: must not be an inner class", element)
|
Diagnostic.Kind.ERROR,
|
||||||
|
"@JsonClass can't be applied to $element: must not be an inner class", element)
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
proto.modality == SEALED -> {
|
kmClass.isSealed -> {
|
||||||
messager.printMessage(
|
messager.printMessage(
|
||||||
ERROR, "@JsonClass can't be applied to $element: must not be sealed", element)
|
Diagnostic.Kind.ERROR, "@JsonClass can't be applied to $element: must not be sealed", element)
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
proto.modality == ABSTRACT -> {
|
kmClass.isAbstract -> {
|
||||||
messager.printMessage(
|
messager.printMessage(
|
||||||
ERROR, "@JsonClass can't be applied to $element: must not be abstract", element)
|
Diagnostic.Kind.ERROR, "@JsonClass can't be applied to $element: must not be abstract",
|
||||||
|
element)
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
proto.visibility == LOCAL -> {
|
kmClass.isLocal -> {
|
||||||
messager.printMessage(
|
messager.printMessage(
|
||||||
ERROR, "@JsonClass can't be applied to $element: must not be local", element)
|
Diagnostic.Kind.ERROR, "@JsonClass can't be applied to $element: must not be local",
|
||||||
|
element)
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val typeVariables = genericTypeNames(proto, typeMetadata.data.nameResolver)
|
val elementHandler = ElementsClassInspector.create(elements, types)
|
||||||
|
val kotlinApi = kmClass.toTypeSpec(elementHandler)
|
||||||
|
val typeVariables = kotlinApi.typeVariables
|
||||||
val appliedType = AppliedType.get(element)
|
val appliedType = AppliedType.get(element)
|
||||||
|
|
||||||
val constructor = primaryConstructor(typeMetadata, elements)
|
val constructor = primaryConstructor(kotlinApi, elements)
|
||||||
|
if (constructor == null) {
|
||||||
|
messager.printMessage(Diagnostic.Kind.ERROR, "No primary constructor found on $element",
|
||||||
|
element)
|
||||||
|
return null
|
||||||
|
}
|
||||||
if (constructor.visibility != KModifier.INTERNAL && constructor.visibility != KModifier.PUBLIC) {
|
if (constructor.visibility != KModifier.INTERNAL && constructor.visibility != KModifier.PUBLIC) {
|
||||||
messager.printMessage(ERROR, "@JsonClass can't be applied to $element: " +
|
messager.printMessage(Diagnostic.Kind.ERROR, "@JsonClass can't be applied to $element: " +
|
||||||
"primary constructor is not internal or public", element)
|
"primary constructor is not internal or public", element)
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
@@ -313,14 +171,19 @@ internal fun targetType(messager: Messager,
|
|||||||
if (supertype.element.kind != ElementKind.CLASS) {
|
if (supertype.element.kind != ElementKind.CLASS) {
|
||||||
continue // Don't load properties for interface types.
|
continue // Don't load properties for interface types.
|
||||||
}
|
}
|
||||||
if (supertype.element.kotlinMetadata == null) {
|
if (supertype.element.getAnnotation(Metadata::class.java) == null) {
|
||||||
messager.printMessage(ERROR,
|
messager.printMessage(Diagnostic.Kind.ERROR,
|
||||||
"@JsonClass can't be applied to $element: supertype $supertype is not a Kotlin type",
|
"@JsonClass can't be applied to $element: supertype $supertype is not a Kotlin type",
|
||||||
element)
|
element)
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
val supertypeProperties = declaredProperties(
|
val supertypeProperties = if (supertype.element == element) {
|
||||||
supertype.element, supertype.resolver, constructor)
|
// We've already parsed this api above, reuse it
|
||||||
|
declaredProperties(supertype.element, constructor, elementHandler, kotlinApi)
|
||||||
|
} else {
|
||||||
|
declaredProperties(
|
||||||
|
supertype.element, constructor, elementHandler)
|
||||||
|
}
|
||||||
for ((name, property) in supertypeProperties) {
|
for ((name, property) in supertypeProperties) {
|
||||||
properties.putIfAbsent(name, property)
|
properties.putIfAbsent(name, property)
|
||||||
}
|
}
|
||||||
@@ -330,224 +193,37 @@ internal fun targetType(messager: Messager,
|
|||||||
constructor = constructor,
|
constructor = constructor,
|
||||||
properties = properties,
|
properties = properties,
|
||||||
typeVariables = typeVariables,
|
typeVariables = typeVariables,
|
||||||
isDataClass = proto.isDataClass,
|
isDataClass = KModifier.DATA in kotlinApi.modifiers,
|
||||||
visibility = proto.visibility.asKModifier())
|
visibility = kotlinApi.modifiers.visibility())
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns the properties declared by `typeElement`. */
|
/** Returns the properties declared by `typeElement`. */
|
||||||
|
@KotlinPoetMetadataPreview
|
||||||
private fun declaredProperties(
|
private fun declaredProperties(
|
||||||
typeElement: TypeElement,
|
typeElement: TypeElement,
|
||||||
typeResolver: TypeResolver,
|
constructor: TargetConstructor,
|
||||||
constructor: TargetConstructor
|
elementHandler: ClassInspector,
|
||||||
|
kotlinApi: TypeSpec = typeElement.toTypeSpec(elementHandler)
|
||||||
): Map<String, TargetProperty> {
|
): Map<String, TargetProperty> {
|
||||||
val typeMetadata: KotlinClassMetadata = typeElement.kotlinMetadata as KotlinClassMetadata
|
|
||||||
val nameResolver = typeMetadata.data.nameResolver
|
|
||||||
val classProto = typeMetadata.data.classProto
|
|
||||||
|
|
||||||
val annotationHolders = mutableMapOf<String, ExecutableElement>()
|
|
||||||
val fields = mutableMapOf<String, VariableElement>()
|
|
||||||
val setters = mutableMapOf<String, ExecutableElement>()
|
|
||||||
val getters = mutableMapOf<String, ExecutableElement>()
|
|
||||||
for (element in typeElement.enclosedElements) {
|
|
||||||
if (element is VariableElement) {
|
|
||||||
fields[element.name] = element
|
|
||||||
} else if (element is ExecutableElement) {
|
|
||||||
when {
|
|
||||||
element.name.startsWith("get") -> {
|
|
||||||
val name = element.name.substring("get".length).decapitalizeAsciiOnly()
|
|
||||||
getters[name] = element
|
|
||||||
}
|
|
||||||
element.name.startsWith("is") -> {
|
|
||||||
val name = element.name.substring("is".length).decapitalizeAsciiOnly()
|
|
||||||
getters[name] = element
|
|
||||||
}
|
|
||||||
element.name.startsWith("set") -> {
|
|
||||||
val name = element.name.substring("set".length).decapitalizeAsciiOnly()
|
|
||||||
setters[name] = element
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val propertyProto = typeMetadata.data.getPropertyOrNull(element)
|
|
||||||
if (propertyProto != null) {
|
|
||||||
val name = nameResolver.getString(propertyProto.name)
|
|
||||||
annotationHolders[name] = element
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val result = mutableMapOf<String, TargetProperty>()
|
val result = mutableMapOf<String, TargetProperty>()
|
||||||
for (property in classProto.propertyList) {
|
for (property in kotlinApi.propertySpecs) {
|
||||||
val name = nameResolver.getString(property.name)
|
val name = property.name
|
||||||
val type = typeResolver.resolve(property.returnType.asTypeName(
|
|
||||||
nameResolver, classProto::getTypeParameter, false))
|
|
||||||
val parameter = constructor.parameters[name]
|
val parameter = constructor.parameters[name]
|
||||||
val fieldElement = fields[name]
|
result[name] = TargetProperty(
|
||||||
val annotationHolder = annotationHolders[name]
|
propertySpec = property,
|
||||||
// Used for setter/getter/is lookups. Guaranteed to be safe because kotlin doesn't allow you to
|
|
||||||
// have both "AAA" and "aAA".
|
|
||||||
val decapitalizedName = name.decapitalizeAsciiOnly()
|
|
||||||
result[name] = TargetProperty(name = name,
|
|
||||||
type = type,
|
|
||||||
parameter = parameter,
|
parameter = parameter,
|
||||||
annotationHolder = annotationHolder?.asFunSpec(),
|
visibility = property.modifiers.visibility(),
|
||||||
field = fieldElement?.asPropertySpec(),
|
jsonName = parameter?.jsonName ?: property.annotations.jsonName()
|
||||||
setter = setters[decapitalizedName]?.asFunSpec(),
|
?: name.escapeDollarSigns()
|
||||||
getter = getters[decapitalizedName]?.asFunSpec(),
|
|
||||||
visibility = property.visibility.asKModifier(),
|
|
||||||
jsonName = jsonName(
|
|
||||||
fieldElement = fieldElement,
|
|
||||||
parameter = parameter,
|
|
||||||
annotationHolder = annotationHolder,
|
|
||||||
name = name
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns the @Json name of this property, or this property's name if none is provided. */
|
private val TargetProperty.isTransient get() = propertySpec.annotations.any { it.className == Transient::class.asClassName() }
|
||||||
private fun jsonName(
|
private val TargetProperty.isSettable get() = propertySpec.mutable || parameter != null
|
||||||
fieldElement: Element?,
|
|
||||||
parameter: TargetParameter?,
|
|
||||||
annotationHolder: ExecutableElement?,
|
|
||||||
name: String): String {
|
|
||||||
val fieldJsonName = fieldElement.jsonName
|
|
||||||
val annotationHolderJsonName = annotationHolder.jsonName
|
|
||||||
val parameterJsonName = parameter?.jsonName
|
|
||||||
|
|
||||||
return when {
|
|
||||||
fieldJsonName != null -> fieldJsonName
|
|
||||||
annotationHolderJsonName != null -> annotationHolderJsonName
|
|
||||||
parameterJsonName != null -> parameterJsonName
|
|
||||||
else -> name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private val Element.name get() = simpleName.toString()
|
|
||||||
|
|
||||||
private fun genericTypeNames(proto: Class, nameResolver: NameResolver): List<TypeVariableName> {
|
|
||||||
return proto.typeParameterList.map { typeParameter ->
|
|
||||||
val possibleBounds = typeParameter.upperBoundList
|
|
||||||
.map { it.asTypeName(nameResolver, proto::getTypeParameter, false) }
|
|
||||||
val typeVar = if (possibleBounds.isEmpty()) {
|
|
||||||
TypeVariableName(
|
|
||||||
name = nameResolver.getString(typeParameter.name),
|
|
||||||
variance = typeParameter.varianceModifier)
|
|
||||||
} else {
|
|
||||||
TypeVariableName(
|
|
||||||
name = nameResolver.getString(typeParameter.name),
|
|
||||||
bounds = *possibleBounds.toTypedArray(),
|
|
||||||
variance = typeParameter.varianceModifier)
|
|
||||||
}
|
|
||||||
return@map typeVar.copy(reified = typeParameter.reified)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private val TypeParameter.varianceModifier: KModifier?
|
|
||||||
get() {
|
|
||||||
return variance.asKModifier().let {
|
|
||||||
// We don't redeclare out variance here
|
|
||||||
if (it == KModifier.OUT) {
|
|
||||||
null
|
|
||||||
} else {
|
|
||||||
it
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a new [PropertySpec] representation of [this].
|
|
||||||
*
|
|
||||||
* This will copy its name, type, visibility modifiers, constant value, and annotations. Note that
|
|
||||||
* Java modifiers that correspond to annotations in kotlin will be added as well (`volatile`,
|
|
||||||
* `transient`, etc`.
|
|
||||||
*
|
|
||||||
* The original `field` ([this]) is stored in [PropertySpec.tag].
|
|
||||||
*/
|
|
||||||
internal fun VariableElement.asPropertySpec(asJvmField: Boolean = false): PropertySpec {
|
|
||||||
require(kind == ElementKind.FIELD) {
|
|
||||||
"Must be a field!"
|
|
||||||
}
|
|
||||||
val modifiers: Set<Modifier> = modifiers
|
|
||||||
val fieldName = simpleName.toString()
|
|
||||||
val propertyBuilder = PropertySpec.builder(fieldName, asType().asTypeName())
|
|
||||||
propertyBuilder.addModifiers(*modifiers.mapNotNull { it.asKModifier() }.toTypedArray())
|
|
||||||
constantValue?.let {
|
|
||||||
if (it is String) {
|
|
||||||
propertyBuilder.initializer(CodeBlock.of("%S", it))
|
|
||||||
} else {
|
|
||||||
propertyBuilder.initializer(CodeBlock.of("%L", it))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
propertyBuilder.addAnnotations(annotationMirrors.map(AnnotationMirror::asAnnotationSpec))
|
|
||||||
propertyBuilder.addAnnotations(modifiers.mapNotNull { it.asAnnotation() })
|
|
||||||
propertyBuilder.tag(this)
|
|
||||||
if (asJvmField && KModifier.PRIVATE !in propertyBuilder.modifiers) {
|
|
||||||
propertyBuilder.addAnnotation(JvmField::class)
|
|
||||||
}
|
|
||||||
return propertyBuilder.build()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a new [AnnotationSpec] representation of [this].
|
|
||||||
*
|
|
||||||
* Identical and delegates to [AnnotationSpec.get], but the original `mirror` is also stored
|
|
||||||
* in [AnnotationSpec.tag].
|
|
||||||
*/
|
|
||||||
internal fun AnnotationMirror.asAnnotationSpec(): AnnotationSpec {
|
|
||||||
return AnnotationSpec.get(this)
|
|
||||||
.toBuilder()
|
|
||||||
.tag(MoreTypes.asTypeElement(annotationType))
|
|
||||||
.build()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a new [FunSpec] representation of [this].
|
|
||||||
*
|
|
||||||
* This will copy its visibility modifiers, type parameters, return type, name, parameters, and
|
|
||||||
* throws declarations.
|
|
||||||
*
|
|
||||||
* The original `method` ([this]) is stored in [FunSpec.tag].
|
|
||||||
*
|
|
||||||
* Nearly identical to [FunSpec.overriding], but no override modifier is added nor are checks around
|
|
||||||
* overridability done
|
|
||||||
*/
|
|
||||||
internal fun ExecutableElement.asFunSpec(): FunSpec {
|
|
||||||
var modifiers: Set<Modifier> = modifiers
|
|
||||||
val methodName = simpleName.toString()
|
|
||||||
val funBuilder = FunSpec.builder(methodName)
|
|
||||||
|
|
||||||
modifiers = modifiers.toMutableSet()
|
|
||||||
funBuilder.jvmModifiers(modifiers)
|
|
||||||
|
|
||||||
typeParameters
|
|
||||||
.map { it.asType() as TypeVariable }
|
|
||||||
.map { it.asTypeVariableName() }
|
|
||||||
.forEach { funBuilder.addTypeVariable(it) }
|
|
||||||
|
|
||||||
funBuilder.returns(returnType.asTypeName())
|
|
||||||
funBuilder.addParameters(ParameterSpec.parametersOf(this))
|
|
||||||
if (isVarArgs) {
|
|
||||||
funBuilder.parameters[funBuilder.parameters.lastIndex] = funBuilder.parameters.last()
|
|
||||||
.toBuilder()
|
|
||||||
.addModifiers(VARARG)
|
|
||||||
.build()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (thrownTypes.isNotEmpty()) {
|
|
||||||
val throwsValueString = thrownTypes.joinToString { "%T::class" }
|
|
||||||
funBuilder.addAnnotation(AnnotationSpec.builder(Throws::class)
|
|
||||||
.addMember(throwsValueString, *thrownTypes.toTypedArray())
|
|
||||||
.build())
|
|
||||||
}
|
|
||||||
|
|
||||||
funBuilder.tag(this)
|
|
||||||
return funBuilder.build()
|
|
||||||
}
|
|
||||||
|
|
||||||
private val TargetProperty.isTransient get() = field != null && field.annotations.any { it.className == Transient::class.asClassName() }
|
|
||||||
private val TargetProperty.isSettable get() = setter != null || parameter != null
|
|
||||||
private val TargetProperty.isVisible: Boolean
|
private val TargetProperty.isVisible: Boolean
|
||||||
get() {
|
get() {
|
||||||
return visibility == KModifier.INTERNAL
|
return visibility == KModifier.INTERNAL
|
||||||
@@ -559,26 +235,24 @@ private val TargetProperty.isVisible: Boolean
|
|||||||
* Returns a generator for this property, or null if either there is an error and this property
|
* Returns a generator for this property, or null if either there is an error and this property
|
||||||
* cannot be used with code gen, or if no codegen is necessary for this property.
|
* cannot be used with code gen, or if no codegen is necessary for this property.
|
||||||
*/
|
*/
|
||||||
internal fun TargetProperty.generator(messager: Messager): PropertyGenerator? {
|
internal fun TargetProperty.generator(
|
||||||
val element = field?.tag<VariableElement>() ?: setter?.tag<ExecutableElement>()
|
messager: Messager,
|
||||||
?: getter!!.tag<ExecutableElement>()
|
sourceElement: TypeElement,
|
||||||
|
elements: Elements
|
||||||
|
): PropertyGenerator? {
|
||||||
if (isTransient) {
|
if (isTransient) {
|
||||||
if (!hasDefault) {
|
if (!hasDefault) {
|
||||||
element?.let {
|
messager.printMessage(
|
||||||
messager.printMessage(
|
Diagnostic.Kind.ERROR, "No default value for transient property $name",
|
||||||
Diagnostic.Kind.ERROR, "No default value for transient property ${this}",
|
sourceElement)
|
||||||
it)
|
|
||||||
}
|
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
return PropertyGenerator(this, DelegateKey(type, emptyList()), true)
|
return PropertyGenerator(this, DelegateKey(type, emptyList()), true)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isVisible) {
|
if (!isVisible) {
|
||||||
element?.let {
|
messager.printMessage(Diagnostic.Kind.ERROR, "property $name is not visible",
|
||||||
messager.printMessage(Diagnostic.Kind.ERROR, "property ${this} is not visible",
|
sourceElement)
|
||||||
it)
|
|
||||||
}
|
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -586,10 +260,12 @@ internal fun TargetProperty.generator(messager: Messager): PropertyGenerator? {
|
|||||||
return null // This property is not settable. Ignore it.
|
return null // This property is not settable. Ignore it.
|
||||||
}
|
}
|
||||||
|
|
||||||
val jsonQualifierMirrors = jsonQualifiers(element)
|
// Merge parameter and property annotations
|
||||||
for (jsonQualifier in jsonQualifierMirrors) {
|
val qualifiers = parameter?.qualifiers.orEmpty() + propertySpec.annotations.qualifiers(elements)
|
||||||
|
for (jsonQualifier in qualifiers) {
|
||||||
// Check Java types since that covers both Java and Kotlin annotations.
|
// Check Java types since that covers both Java and Kotlin annotations.
|
||||||
val annotationElement = jsonQualifier.tag<TypeElement>() ?: continue
|
val annotationElement = elements.getTypeElement(jsonQualifier.className.canonicalName)
|
||||||
|
?: continue
|
||||||
annotationElement.getAnnotation(Retention::class.java)?.let {
|
annotationElement.getAnnotation(Retention::class.java)?.let {
|
||||||
if (it.value != RetentionPolicy.RUNTIME) {
|
if (it.value != RetentionPolicy.RUNTIME) {
|
||||||
messager.printMessage(Diagnostic.Kind.ERROR,
|
messager.printMessage(Diagnostic.Kind.ERROR,
|
||||||
@@ -604,7 +280,7 @@ internal fun TargetProperty.generator(messager: Messager): PropertyGenerator? {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val jsonQualifierSpecs = jsonQualifierMirrors.map {
|
val jsonQualifierSpecs = qualifiers.map {
|
||||||
it.toBuilder()
|
it.toBuilder()
|
||||||
.useSiteTarget(AnnotationSpec.UseSiteTarget.FIELD)
|
.useSiteTarget(AnnotationSpec.UseSiteTarget.FIELD)
|
||||||
.build()
|
.build()
|
||||||
@@ -614,57 +290,21 @@ internal fun TargetProperty.generator(messager: Messager): PropertyGenerator? {
|
|||||||
DelegateKey(type, jsonQualifierSpecs))
|
DelegateKey(type, jsonQualifierSpecs))
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns the JsonQualifiers on the field and parameter of this property. */
|
private fun List<AnnotationSpec>?.qualifiers(elements: Elements): Set<AnnotationSpec> {
|
||||||
private fun TargetProperty.jsonQualifiers(element: Element?): Set<AnnotationSpec> {
|
if (this == null) return setOf()
|
||||||
val elementQualifiers = element.qualifiers
|
return filterTo(mutableSetOf()) {
|
||||||
val annotationHolderQualifiers = annotationHolder?.tag<ExecutableElement>().qualifiers
|
elements.getTypeElement(it.className.toString()).getAnnotation(JSON_QUALIFIER) != null
|
||||||
val parameterQualifiers = parameter?.qualifiers.orEmpty()
|
|
||||||
|
|
||||||
// TODO(jwilson): union the qualifiers somehow?
|
|
||||||
return when {
|
|
||||||
elementQualifiers.isNotEmpty() -> elementQualifiers.mapTo(mutableSetOf(), AnnotationMirror::asAnnotationSpec)
|
|
||||||
annotationHolderQualifiers.isNotEmpty() -> annotationHolderQualifiers.mapTo(
|
|
||||||
mutableSetOf(), AnnotationMirror::asAnnotationSpec)
|
|
||||||
parameterQualifiers.isNotEmpty() -> parameterQualifiers
|
|
||||||
else -> setOf()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Modifier.asKModifier(): KModifier? {
|
/** Gross, but we can't extract values from AnnotationSpecs by member names alone. */
|
||||||
return when (this) {
|
private fun List<AnnotationSpec>?.jsonName(): String? {
|
||||||
Modifier.PUBLIC -> KModifier.PUBLIC
|
if (this == null) return null
|
||||||
Modifier.PROTECTED -> KModifier.PROTECTED
|
return find { it.className == JSON }?.let {
|
||||||
Modifier.PRIVATE -> KModifier.PRIVATE
|
it.members[0].toString().removePrefix("name = \"").removeSuffix("\"")
|
||||||
Modifier.ABSTRACT -> KModifier.ABSTRACT
|
|
||||||
FINAL -> KModifier.FINAL
|
|
||||||
else -> null
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Modifier.asAnnotation(): AnnotationSpec? {
|
private fun String.escapeDollarSigns(): String {
|
||||||
return when (this) {
|
return replace("\$", "\${\'\$\'}")
|
||||||
DEFAULT -> JvmDefault::class.asAnnotationSpec()
|
|
||||||
STATIC -> JvmStatic::class.asAnnotationSpec()
|
|
||||||
TRANSIENT -> Transient::class.asAnnotationSpec()
|
|
||||||
VOLATILE -> Volatile::class.asAnnotationSpec()
|
|
||||||
SYNCHRONIZED -> Synchronized::class.asAnnotationSpec()
|
|
||||||
NATIVE -> JvmDefault::class.asAnnotationSpec()
|
|
||||||
else -> null
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun <T : Annotation> KClass<T>.asAnnotationSpec(): AnnotationSpec {
|
|
||||||
return AnnotationSpec.builder(this).build()
|
|
||||||
}
|
|
||||||
|
|
||||||
private val Element?.qualifiers: Set<AnnotationMirror>
|
|
||||||
get() {
|
|
||||||
if (this == null) return setOf()
|
|
||||||
return AnnotationMirrors.getAnnotatedAnnotations(this, JsonQualifier::class.java)
|
|
||||||
}
|
|
||||||
|
|
||||||
private val Element?.jsonName: String?
|
|
||||||
get() {
|
|
||||||
if (this == null) return null
|
|
||||||
return getAnnotation(Json::class.java)?.name
|
|
||||||
}
|
|
||||||
|
@@ -18,6 +18,7 @@ package com.squareup.moshi.kotlin.codegen
|
|||||||
import com.tschuchort.compiletesting.KotlinCompilation
|
import com.tschuchort.compiletesting.KotlinCompilation
|
||||||
import com.tschuchort.compiletesting.SourceFile
|
import com.tschuchort.compiletesting.SourceFile
|
||||||
import com.tschuchort.compiletesting.SourceFile.Companion.kotlin
|
import com.tschuchort.compiletesting.SourceFile.Companion.kotlin
|
||||||
|
import com.squareup.kotlinpoet.metadata.KotlinPoetMetadataPreview
|
||||||
import org.assertj.core.api.Assertions.assertThat
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
import org.junit.Ignore
|
import org.junit.Ignore
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
@@ -25,6 +26,7 @@ import org.junit.Test
|
|||||||
import org.junit.rules.TemporaryFolder
|
import org.junit.rules.TemporaryFolder
|
||||||
|
|
||||||
/** Execute kotlinc to confirm that either files are generated or errors are printed. */
|
/** Execute kotlinc to confirm that either files are generated or errors are printed. */
|
||||||
|
@UseExperimental(KotlinPoetMetadataPreview::class)
|
||||||
class JsonClassCodegenProcessorTest {
|
class JsonClassCodegenProcessorTest {
|
||||||
@Rule @JvmField var temporaryFolder: TemporaryFolder = TemporaryFolder()
|
@Rule @JvmField var temporaryFolder: TemporaryFolder = TemporaryFolder()
|
||||||
|
|
||||||
|
@@ -1,47 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2018 Square, Inc.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package com.squareup.moshi.kotlin.codegen
|
|
||||||
|
|
||||||
import com.google.common.truth.Truth.assertThat
|
|
||||||
import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.plusParameter
|
|
||||||
import com.squareup.kotlinpoet.WildcardTypeName
|
|
||||||
import com.squareup.kotlinpoet.asClassName
|
|
||||||
import com.squareup.moshi.kotlin.codegen.api.TypeResolver
|
|
||||||
import org.junit.Test
|
|
||||||
|
|
||||||
class TypeResolverTest {
|
|
||||||
private val resolver = TypeResolver()
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun ensureClassNameNullabilityIsPreserved() {
|
|
||||||
assertThat(resolver.resolve(Int::class.asClassName().copy(nullable = true)).isNullable).isTrue()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun ensureParameterizedNullabilityIsPreserved() {
|
|
||||||
val nullableTypeName = List::class.plusParameter(String::class).copy(nullable = true)
|
|
||||||
|
|
||||||
assertThat(resolver.resolve(nullableTypeName).isNullable).isTrue()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun ensureWildcardNullabilityIsPreserved() {
|
|
||||||
val nullableTypeName = WildcardTypeName.producerOf(List::class.asClassName())
|
|
||||||
.copy(nullable = true)
|
|
||||||
|
|
||||||
assertThat(resolver.resolve(nullableTypeName).isNullable).isTrue()
|
|
||||||
}
|
|
||||||
}
|
|
9
pom.xml
9
pom.xml
@@ -31,15 +31,13 @@
|
|||||||
<java.version>1.7</java.version>
|
<java.version>1.7</java.version>
|
||||||
|
|
||||||
<!-- Dependencies -->
|
<!-- Dependencies -->
|
||||||
<auto-common.version>0.10</auto-common.version>
|
|
||||||
<auto-service.version>1.0-rc5</auto-service.version>
|
<auto-service.version>1.0-rc5</auto-service.version>
|
||||||
<dokka.version>0.9.17</dokka.version>
|
<dokka.version>0.9.17</dokka.version>
|
||||||
<incap.version>0.2</incap.version>
|
<incap.version>0.2</incap.version>
|
||||||
<okio.version>1.16.0</okio.version>
|
<okio.version>1.16.0</okio.version>
|
||||||
<okio2.version>2.1.0</okio2.version>
|
<okio2.version>2.1.0</okio2.version>
|
||||||
<kotlin.version>1.3.40</kotlin.version>
|
<kotlin.version>1.3.40</kotlin.version>
|
||||||
<kotlinpoet.version>1.3.0</kotlinpoet.version>
|
<kotlinpoet.version>1.4.0</kotlinpoet.version>
|
||||||
<kotlin-metadata.version>1.4.0</kotlin-metadata.version>
|
|
||||||
<maven-assembly.version>3.1.0</maven-assembly.version>
|
<maven-assembly.version>3.1.0</maven-assembly.version>
|
||||||
|
|
||||||
<!-- Test Dependencies -->
|
<!-- Test Dependencies -->
|
||||||
@@ -118,11 +116,6 @@
|
|||||||
<artifactId>kotlin-annotation-processing-embeddable</artifactId>
|
<artifactId>kotlin-annotation-processing-embeddable</artifactId>
|
||||||
<version>${kotlin.version}</version>
|
<version>${kotlin.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
|
||||||
<groupId>me.eugeniomarletti.kotlin.metadata</groupId>
|
|
||||||
<artifactId>kotlin-metadata</artifactId>
|
|
||||||
<version>${kotlin-metadata.version}</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.google.testing.compile</groupId>
|
<groupId>com.google.testing.compile</groupId>
|
||||||
<artifactId>compile-testing</artifactId>
|
<artifactId>compile-testing</artifactId>
|
||||||
|
Reference in New Issue
Block a user